Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Mayb3Nots opened this issue Jun 20, 2021 · 11 comments
Closed

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

Mayb3Nots opened this issue Jun 20, 2021 · 11 comments

Comments

@Mayb3Nots
Copy link

Mayb3Nots commented Jun 20, 2021

When you call controller.refresh() you have to wait till the previous addPageRequestListener has 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
Copy link
Owner

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
Copy link
Author

Mayb3Nots commented Jun 20, 2021

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 #79 (comment). 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
Copy link
Owner

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
Copy link
Author

Mayb3Nots commented Jun 21, 2021

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
Copy link
Owner

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

@Mayb3Nots
Copy link
Author

Mayb3Nots commented Jun 29, 2021

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
Copy link
Owner

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
Copy link
Author

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
Copy link
Author

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
Copy link
Owner

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
Copy link

rupinderjeet commented Jan 4, 2022

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants