Reputation: 30501
As a Provider user I've always wondered about using Riverpod since they're from the same author. I've been playing around with it and like the use of with()
but I'm having problems implementing an infinite loading scroll in my test environment.
I'm pretty sure I'm thinking about this the wrong way but there are some things I find confusing. I'm able to load my first data correctly but loading any succeeding pages from there won't work. These 3 things come to mind:
fetchNextBatch()
, what's the right way to append to state?page
as an attribute of RecipeData
but should this be a StateProvider<int>
instead? Will it cause problems with rebuilds?I realize this is a rundown topic that's already been discussed at length but this is just me kicking the wheels trying to get a firm grip on its features.
@riverpod
class RecipeData extends _$RecipeData {
int page = 1;
final int _limit = 8;
@override
Future<List<Recipe>> build() async {
final int offset = (page - 1) * _limit;
final List<Recipe> data = await RecipeService.fetchFromApi(ref, _limit, offset);
return data;
}
Future<List<Recipe>> fetchNextBatch() async {
// TODO: Don't fetch if another fetch is in progress
incrPage();
final int offset = (page - 1) * _limit;
final awaiteddata = await RecipeService.fetchFromApi(ref, _limit, offset);
// Why is this wrong?
state = [...state, ...awaiteddata];
}
void incrPage() => page++;
void resetPage() => page = 0;
}
@override
Widget build(BuildContext context) {
final recipes = ref.watch(recipeDataProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Playground')
),
body: Column(
children: [
...
// when() is great
recipes.when(
data: (data) => ListView.builder(...),
loading: () => const CircularProgressIndicator(),
error: (err, _) => Text(err.toString()),
),
],
),
);
}
@freezed
class Recipe with _$Recipe {
const factory Recipe({
required String name,
required String cuisine,
required String image,
}) = _Recipe;
factory Recipe.fromJson(Map<String, Object?> json) => _$RecipeFromJson(json);
}
I also created this in case I needed it which is just another provider that has alldata
and page
in it.
@freezed
class RecipeResponse with _$RecipeResponse {
const factory RecipeResponse({
@Default([]) final List<Recipe> alldata,
@Default(1) final int page,
}) = _RecipeResponse;
}
Upvotes: 0
Views: 369
Reputation: 340
in your case
Future<void> fetchNextBatch() async {
incrPage();
final int offset = (page - 1) * _limit;
final awaiteddata = await RecipeService.fetchFromApi(ref, _limit, offset);
final previousState = await future;
state = AsyncData([...previousState, ...awaiteddata]);
}
there is my sample
enum ProductFilterEnum {
onSale,
review,
reject,
notSale,
}
//filter state
@riverpod
class ProductFilter extends _$ProductFilter {
@override
ProductFilterEnum build() {
return ProductFilterEnum.onSale;
}
}
//search state
@riverpod
class ProductSearch extends _$ProductSearch {
@override
String build() {
return "";
}
}
//sort state
@riverpod
class ProductSort extends _$ProductSort {
@override
bool build() {
return false;
}
}
@riverpod
class Product extends _$Product {
int _page = 0;
@override
Future<List<ProducModel>> build() async {
final result = await fetch(_page);
return result;
}
Future<void> getNextPage() async {
_page++;
final result = await fetch(_page);
final previousState = await future;
state = AsyncData([...previousState, ...result]);
}
//api
Future<List<ProducModel>> fetch(int page) async {
final filter = ref.watch(productFilterProvider);
final search = ref.watch(productSearchProvider);
final sort = ref.watch(productSortProvider);
await Future.delayed(Duration(seconds: 1));
return [ProducModel(name: "$page $search $filter")];
}
}
in widget
//watch for change
Widget tabBarView() {
final providerList = ref.watch(productProvider);
final search = ref.watch(productSearchProvider);
final list = providerList.value ?? [];
if (providerList.isLoading) {
return const Center(child: CircularProgressIndicator());
}
//when search empty
if (search.isNotEmpty && list.isEmpty) {
return Center(
child: Text(
AppLocalizations.of(context)!.translate('product_search_empty'),
style: TextStyle(fontSize: 14.0, color: AppStyle.grayColor2, fontWeight: FontWeight.w300),
),
);
}
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
return listItem(item);
},
);
}
//tabBarView will change after read
Textfield(
controller: _search,
onEditingComplete: () {
ref.read(productSearchProvider.notifier).state = _search.text;
},
textInputAction: TextInputAction.search,
),
Upvotes: 0