EdsonBueno / infinite_scroll_pagination

Flutter package to help you lazily load and display pages of items as the user scrolls down your screen.
https://pub.dev/packages/infinite_scroll_pagination
MIT License
634 stars 215 forks source link

Cant refresh again when previous refresh is still loading. #108

Closed Mayb3Nots closed 3 years ago

Mayb3Nots commented 3 years ago

When you call controller.refresh() you have to wait till the previous addPageRequestListenerhas finished and append. How do you cancel the previous future and just refresh again?

This happened to me when the user wants to switch filters quickly. So every time a filter setting is applied, controller.refresh() is called with the new setting. But when the refresh is called too quickly before the previous one can complete it doesn't register and addPageRequestListener isn't called.

EdsonBueno commented 3 years ago

Hi @Mayb3Nots . This has to be done on your end. Realize that the package doesn't even know you're using Futures. But I can help you with that.

Check this link. The same _activeCallbackIdentity mechanism used there will solve your issue. Thank you so much.

Mayb3Nots commented 3 years ago

Hi @EdsonBueno Thanks for the quick response and the amazing package! After looking at the mechanism you linked and tried to implement it, it still didn't work. It seems like someone had the same issue like me https://github.com/EdsonBueno/infinite_scroll_pagination/issues/79#issuecomment-823605108. Same case as him, my widget doesn't rebuild it just updates the API parameter. And it doesn't call the fetch page function again. I wonder how he solved his issue, any advice?

pagingController.addPageRequestListener((pageKey) async {
      final callBackIdentity = Object();
      _activeCallbackIdentitiy = callBackIdentity;
      try {
        final result = await wooCommerceAPI
            .get('orders?page=$pageKey&status=$customStatus')
            .then((value) {
          final listOfOrder = <WooOrder>[];
          for (var v in value) {
            listOfOrder.add(WooOrder.fromJson(v));
          }
          return listOfOrder;
        });

        final lastPage = result.length < 10;
        if (callBackIdentity == _activeCallbackIdentitiy) {
          if (lastPage) {
            pagingController.appendLastPage(result);
          } else {
            final nextPageKey = pageKey + 1;
            pagingController.appendPage(result, nextPageKey);
          }
        }
      } catch (e, stackTrace) {
        pagingController.error = e.toString();
        handleError(e, stackTrace);
      }
    });
EdsonBueno commented 3 years ago

I didn't understand what you mean with "it just updates the API parameter". Can you provide me a minimal sample reproducing the issue?

Mayb3Nots commented 3 years ago

So you see the customStatus variable? When they click on a button the customStatus changes to call a different 'status' basicll a different API call. When this happens the addPageRequestListener doesn't get called when it is still loading the current API call. I will try to provide a minimal sample reproduction code.

EdsonBueno commented 3 years ago

Hi @Mayb3Nots . I'm sorry, but I'll have to wait for the minimal sample project. Thank you!

Mayb3Nots commented 3 years ago

Sorry for late reply here is the reproduction code. If you click on refresh button when its loading it wont call _fetchPage.

import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _key = GlobalKey<ScaffoldMessengerState>();
  late PagingController<int, Text> _controller;
  int index = 0;
  @override
  void initState() {
    super.initState();
    _controller = PagingController(firstPageKey: 0)
      ..addPageRequestListener((pageKey) => _fetchPage(pageKey));
  }

  Future<void> _fetchPage(int pageKey) async {
    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
      _key.currentState?.clearSnackBars();
      _key.currentState?.showSnackBar(SnackBar(
        content: Text("Refresh called"),
        duration: Duration(seconds: 1),
      ));
    });
    await Future.delayed(Duration(seconds: 5));

    _controller.appendLastPage([Text(pageKey.toString())]);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      scaffoldMessengerKey: _key,
      home: SafeArea(
        child: Scaffold(
          body: Column(
            children: [
              ElevatedButton(
                child: Text('Refresh'),
                onPressed: () {
                  index = 10;
                  _controller.refresh();
                },
              ),
              Expanded(
                child: PagedListView(
                  builderDelegate: PagedChildBuilderDelegate<Text>(
                      itemBuilder: (context, item, index) => Center(
                            child: Row(
                              children: [item, Text(index.toString())],
                            ),
                          )),
                  pagingController: _controller,
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
EdsonBueno commented 3 years ago

I see it now @Mayb3Nots , thank you for the sample code. So, in you case, you can call _fetchPage directly from your onPressed if _controller.value.status == PagingStatus.loadingFirstPage.

Mayb3Nots commented 3 years ago

If you dont mind me asking, why not add a boolean property (eg. overrideLoadingFirstPage) that allows override of the PagingStatus.loadingFirstPage so that it can be called and updated even when its loading?

Mayb3Nots commented 3 years ago

Using your suggestion worked but it came with some challenges and extra code like making sure the previous Future doesn't complete and append the list. I would like it if this was a built-in feature. I don't mind helping to contribute if you could point me in a direction to where I can implement this.

EdsonBueno commented 3 years ago

If you dont mind me asking, why not add a boolean property (eg. overrideLoadingFirstPage) that allows override of the PagingStatus.loadingFirstPage so that it can be called and updated even when its loading?

I'm afraid it's not that easy, this is not how the package works. Your problem is related to your use case and how your API works, it's not related to pagination or infinite scroll. Adding more public APIs to handle that scenario would only make the package more complicated.

Thank you so much.

rupinderjeet commented 2 years ago

@Mayb3Nots Setting an error on PagingController and then, refreshing it is working for me.

pagingController.error = "Cancelled";
pagingController.refresh();

After this, you will only need to cancel your Future.


I am doing this using async package's CancelableOperation.

networkOp = async.CancelableOperation.fromFuture(
      yourFuture,
      onCancel: () {
        pagingController.error = "Cancelled";
      });

Then, when refreshing,

// this will call cancel on `CancelableOperation` and set error on `PaginationController`
networkOp?.cancel(); 
pagingController.refresh();

@EdsonBueno do you think this solution might have any side-effects?

newToFlutter-pleaseBeKind