Closed Nazarii77 closed 7 months ago
The package does not prevent running multiple refreshes at once, which can lead to the data being messed up.
You can try to prevent multiple refreshes from happening by having a boolean or mutex control your refresh function.
Thanks, @clragon, flags helped, but not fully. If the page size is small like 3, the page 0 and 1 are loading almost simultaniously on big screens. Original counter messes up and sometimes I see 1 page before page 0. Also there is need to block refresh if we currently loading some big page, like 10 items of huge json`s
//in class declaration bool _isLoadingStarted = false;
//in init
@override
void initState() {
super.initState();
_pagingController.addPageRequestListener((pageKey) async {
if ((_pagingController.value.status == PagingStatus.loadingFirstPage ||
_pagingController.value.status == PagingStatus.ongoing) &&
_isLoadingStarted) return;
_isLoadingStarted = true;
await _fetchPage(Provider.of<
YourProvider>
(context, listen: false),
pageKey, widget.YourItemId);
});
}
//on refresh
onRefresh: () async { if ((_pagingController.value.status == PagingStatus.loadingFirstPage || _pagingController.value.status == PagingStatus.ongoing) && _isLoadingStarted) return; extensions.clear(); _pagingController.refresh(); },
New to flutter, but this seems like a critical issue? I would've hoped that there would be some locking mechanism that would be in place to prevent something like this
The package does not prevent running multiple refreshes at once, which can lead to the data being messed up.
You can try to prevent multiple refreshes from happening by having a boolean or mutex control your refresh function.
Is there an example on the proper way to do this?
Thanks, @clragon, flags helped, but not fully. If the page size is small like 3, the page 0 and 1 are loading almost simultaniously on big screens. Original counter messes up and sometimes I see 1 page before page 0. Also there is need to block refresh if we currently loading some big page, like 10 items of huge json`s
//in class declaration bool _isLoadingStarted = false;
//in init @OverRide void initState() { super.initState(); _pagingController.addPageRequestListener((pageKey) async { if ((_pagingController.value.status == PagingStatus.loadingFirstPage || _pagingController.value.status == PagingStatus.ongoing) && _isLoadingStarted) return; _isLoadingStarted = true; await _fetchPage(Provider.of
<
YourProvider>
(context, listen: false), pageKey, widget.YourItemId); }); }//on refresh
onRefresh: () async { if ((_pagingController.value.status == PagingStatus.loadingFirstPage || _pagingController.value.status == PagingStatus.ongoing) && _isLoadingStarted) return; extensions.clear(); _pagingController.refresh(); },
@Nazarii77 how would one implement this inside of _fetchpage
?
I think I have the same issue when I refresh the page controller after a search.
I have also this same issue
I have the same issue when trying to implement searching.
There is my code for paged list view and search. Search triggered inside didUpdateWidget
and calling refresh()
.
class PostsFeedListView extends StatefulWidget {
const PostsFeedListView({
Key? key,
required this.classId,
required this.basePostsRequest,
this.pageSize = 3,
this.controller,
}) : assert(pageSize > 0),
super(key: key);
final String classId;
final PostsRequest basePostsRequest;
final int pageSize;
final ScrollController? controller;
@override
State<PostsFeedListView> createState() => _PostsFeedListViewState();
}
class _PostsFeedListViewState extends State<PostsFeedListView>
with LocalizationsMixin {
final PagingController<int, Post> _pagingController =
PagingController(firstPageKey: 0);
final PagingController<int, Post> _pinnedListPagingController =
PagingController(firstPageKey: 0);
late PostsRequest _postsRequest;
late PostsRequest _pinnedPostsRequest;
bool _isShowPinned = true;
Completer? _refreshCompleter;
bool _isLoading = true;
bool _isPinnedLoading = true;
@override
void initState() {
super.initState();
_initPostRequests();
_initControllers();
}
@override
void didUpdateWidget(covariant PostsFeedListView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.basePostsRequest != oldWidget.basePostsRequest) {
if (_refreshCompleter != null) {
_refreshCompleter?.future.then((_) async {
_isShowPinned = true;
_updateControllers();
});
} else {
_updateControllers();
}
}
}
void _updateControllers() {
_disposeControllers();
_initPostRequests();
_initControllers();
_onRefresh();
}
@override
void dispose() {
_disposeControllers();
super.dispose();
}
void _initControllers() {
_pinnedListPagingController.addPageRequestListener(_fetchPageOfPinnedPosts);
_pagingController.addPageRequestListener(_fetchPage);
}
void _disposeControllers() {
_pinnedListPagingController
.removePageRequestListener(_fetchPageOfPinnedPosts);
_pagingController.removePageRequestListener(_fetchPage);
}
void _initPostRequests() {
_postsRequest = widget.basePostsRequest.copyWith(pinnedOnly: false);
_pinnedPostsRequest = widget.basePostsRequest.copyWith(pinnedOnly: true);
}
void _fetchPage(int pageKey) {
BlocProvider.of<FeedBloc>(context).add(
GetPosts(
widget.classId,
pageParameters: PageParameters(
number: pageKey,
size: widget.pageSize,
),
requestParameters: _postsRequest,
),
);
}
void _fetchPageOfPinnedPosts(int pageKey) {
BlocProvider.of<FeedBloc>(context).add(
GetPosts(
widget.classId,
pageParameters: PageParameters(
number: pageKey,
size: widget.pageSize,
),
requestParameters: _pinnedPostsRequest,
),
);
}
@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<FeedBloc, FeedState>(
listener: _feedListener,
),
BlocListener<PostsBloc, PostsState>(
listener: _postsUpdatesListener,
),
],
child: RefreshIndicator(
onRefresh: _onRefresh,
child: CustomScrollView(
slivers: [
if (_isShowPinned)
PagedSliverList(
pagingController: _pinnedListPagingController,
builderDelegate: _getPageDelegate(),
),
PagedSliverList<int, Post>(
pagingController: _pagingController,
builderDelegate: _getPageDelegate(),
),
],
),
),
);
}
void _postsUpdatesListener(context, state) async {
if (state is PostPublished && state.classId == widget.classId ||
state is PostScheduled && state.classId == widget.classId) {
widget.controller?.jumpTo(0);
await _onRefresh();
}
if (state is PostUpdated && state.classId == widget.classId) {
BlocProvider.of<PostsBloc>(context).add(GetPost(
state.classId,
state.postId,
));
}
if (state is PollUpdated && state.classId == widget.classId) {
BlocProvider.of<PostsBloc>(context).add(GetPost(
state.classId,
state.postId,
));
}
}
Future<void> _onRefresh() {
_isLoading = true;
_isPinnedLoading = true;
_refreshCompleter?.complete();
_pinnedListPagingController.refresh();
_pagingController.refresh();
_refreshCompleter = Completer();
return _refreshCompleter!.future;
}
PagedChildBuilderDelegate<Post> _getPageDelegate() {
return PagedChildBuilderDelegate<Post>(
itemBuilder: (context, item, index) => PostListTile(
key: ValueKey(widget.classId + item.id),
post: item,
classId: widget.classId,
),
animateTransitions: true,
noItemsFoundIndicatorBuilder: (context) => Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
localizations.thereIsNothingHere,
style: Theme.of(context).textTheme.headline5,
),
),
),
);
}
void _feedListener(context, state) {
if (state is PostsReceived && state.requestParameters == _postsRequest) {
_isLoading = false;
_updateCompleter();
final isLastPage = state.page.content.length < widget.pageSize;
if (isLastPage) {
_pagingController.appendLastPage(state.page.content);
} else {
_pagingController.appendPage(
state.page.content,
state.page.number + 1,
);
}
} else if (state is PostsReceived &&
state.requestParameters == _pinnedPostsRequest) {
_isPinnedLoading = false;
_updateCompleter();
final isLastPage = state.page.content.length < widget.pageSize;
if (isLastPage) {
_pinnedListPagingController.appendLastPage(state.page.content);
if (_pinnedListPagingController.itemList?.isEmpty ?? true) {
setState(() {
_isShowPinned = false;
});
}
} else {
_pinnedListPagingController.appendPage(
state.page.content,
state.page.number + 1,
);
}
}
}
void _updateCompleter() {
if (!_isLoading && !_isPinnedLoading) {
_refreshCompleter?.complete();
_refreshCompleter = null;
}
}
}
if (pageKey == 1) {
if (_pagingController.itemList != null) {
_pagingController.itemList!.clear();
}
}
after adding this to my _fetchPageMethod
_fetchPage(int pageKey) async {
try {
final newItems = await Repository.getData(pageNumber: pageKey);
if (newItems == null) {
return;
}
if (pageKey == 1) {
if (_pagingController.itemList != null) {
_pagingController.itemList!.clear();
}
}
final isLastPage = newItems.length < _pageSize;
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + 1;
_pagingController.appendPage(newItems, nextPageKey);
}
} catch (error) {
_pagingController.error = error;
}
}
hope this helped
I have also faced this issue then I find out that we are facing this issue because whenever we call _pageController.refresh(); if there is an already ongoing call so it will first complete that call and after that, it calls the refresh function due to which we got the already ongoing request's items first and then the refreshed items. so to get rid of this I just added a small functionality is whenever it loads the first page then it also clears the existing item list
Added this code in the _fetchPage method and it works fine for me
if (pageKey == 1) { if (_pagingController.itemList != null) { _pagingController.itemList!.clear(); } }
after adding this to my _fetchPageMethod
_fetchPage(int pageKey) async { try { final newItems = await Repository.getData(pageNumber: pageKey); if (newItems == null) { return; } if (pageKey == 1) { if (_pagingController.itemList != null) { _pagingController.itemList!.clear(); } } final isLastPage = newItems.length < _pageSize; if (isLastPage) { _pagingController.appendLastPage(newItems); } else { final nextPageKey = pageKey + 1; _pagingController.appendPage(newItems, nextPageKey); } } catch (error) { _pagingController.error = error; } }
hope this helped
I tweaked your answer a bit and it now works perfectly for me.
fetchPage(int pageKey) async {
try {
final newItems = await Repository.getData(pageNumber: pageKey);
if (newItems == null) {
return;
}
if (pageKey == 1) {
if (_pagingController.itemList != null) {
_pagingController.itemList!.clear();
}
}
final isLastPage = newItems.length < _pageSize;
//I only add an element if I am on the first page and the list is empty or if I am the page different from 1 and the list is not //empty
if ((pageKey == 1 && _pagingController.itemList == null) ||
(pageKey > 1 && _pagingController.itemList != null)) {
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + 1;
_pagingController.appendPage(newItems, nextPageKey);
}
}
} catch (error) {
_pagingController.error = error;
}
}
same issue goes with me, when i pass quickly search value or quickly clear value then refresh() skip the last some calls. and list not show proper items, so i used https://pub.dev/packages/debounce_throttle, its delay the refresh() for some time and then call next refresh()
Debouncer<String> debouncer = Debouncer<String>(const Duration(milliseconds: 200),initialValue: '');
TextEditingController searchController = TextEditingController();
void controllerListener(){
searchController.addListener(() {
debouncer.value = searchController.text;
});
}
in textfield
onChanged: (value) {
controller.debouncer.values.listen((event) {
controller.searchedPersonName = event;
controller.pagingController.refresh();
});
},
this is worked for me :)
Linking to better solution: https://github.com/EdsonBueno/infinite_scroll_pagination/issues/108#issuecomment-1004731033
If you refresh multiple times quickly - some of the refreshes may fail and skip page