带有infinite_scroll_pagination的Flutter Riverpod [需要建议]

问题描述 投票:0回答:1

我现在陷入了一些困惑。关于这个 Flutter 问题的任何帮助都会非常有帮助。非常感谢!

最近我爱上了Riverpod。我到处都在使用 Riverpod。Riverpod 非常强大,但我对使用 Riverpod 很菜鸟。我总是担心我是否正确使用它。

我希望你了解我是如何做到的,然后纠正我的错误或帮助我优化我的代码。

Rest API 响应

{
    "status": 200,
    "msg": "OK",
    "description": "News Search Results",
    "data": {
        "per_page": 10,
        "from": 1,
        "to": 10,
        "total": 19,
        "current_page": 1,
        "last_page": 2,
        "prev_page_url": null,
        "first_page_url": "http://xxxxxx/api/v1/search-news-all?page=1",
        "next_page_url": "http://xxxxxx/api/v1/search-news-all?page=2",
        "last_page_url": "http://xxxxxx/api/v1/search-news-all?page=2",
        "posts": [
            {
                ...
            },
            {
                ...
            },
            
            ...
            
        ]
    }
}

API方法

FutureEither<({List<NewsPreviewModel> newsData, String? nextPageKey})> searchNewsBykeyword({
    required String query,
    required int page,
  }) async {
    try {
      final url = "$_baseAPIUrl/search-news-all?q=$query&page=$page";
      final response = await sendRequest(
        url,
        'GET',
      );
      // checking api status code. if it's not 200, there is some network or req method related issue
      if (response.statusCode == 200) {
      
       /// [decoding response]
        final decodedResponse = jsonDecode(response.body);
        
        /// [checking response status. if it's not 200 this could be token related issue]
        if (decodedResponse['status'] == 200) {
        
        /// [converting to data model]
          final List<NewsPreviewModel> data = List<NewsPreviewModel>.from(
            (decodedResponse['data']['posts'] as List).map(
              (e) => NewsPreviewModel.fromJson(e as Map<String, dynamic>),
            ),
          ).toList();
          
         /// [returning data with with next page key]
          return right(
            (
              newsData: data,
              nextPageKey: decodedResponse['data']['next_page_url'] as String?,
            ),
          );
        } else {
        
        /// [decodedResponse['status'] is not 200]
          return left(
            Failure(
              message: decodedResponse['description'],
            ),
          );
        }
      } else {
      // response.statusCode is not 200
        final res = getCustomHttpErrorMessage(response.statusCode);

        throw HttpException(res);
      }
    } catch (e, st) {
      LoggerManager.e("[searchNewsBykeyword] $e $st");
      final res = getErrorMessage(e);
      
      // return Failure
      return left(
        Failure(
          message: res,
          stackTrace: st,
        ),
      );
    }
  }

控制器文件

这里我使用 infinite_scroll_pagination 4.0.0 包来显示分页数据。 我还使用 Riverpod FutureProvider 来处理未来的数据。

final keywordSearchResultNewsPaginationControllerProvider = FutureProvider.family((ref, String slug) {

  /// [this is using for accessing api method]
  final newsAPI = ref.watch(newsAPIProvider);
  
  /// [defining controller]
  /// [first page key is 1]
  final controller = PagingController<int, NewsPreviewModel>(firstPageKey: 1);
  
  /// [this part generates new data when user reach bottom of the screen]
  /// i mean when new is needed this method will fetch the new page data
  controller.addPageRequestListener((pageKey) {
    newsAPI
        .searchNewsBykeyword(
      query: slug,
      page: pageKey,
    )
        .then((apiData) {
      apiData.fold(
        (l) {
          controller.error = l.message;
        },
        (r) {
          /// [It will fetch data until the next pagekey is null] [see api response for nextpagekey]
          final isLastPage = r.nextPageKey == null;
          if (isLastPage) {
            controller.appendLastPage(r.newsData);
          } else {
            final nextPageKey = pageKey + 1;
            controller.appendPage(
              r.newsData,
              nextPageKey,
            );
          }
        },
      );
    }).catchError((error) {
      controller.error = error;
    });
  });
  /// [returning controller which is a PagingController from infinite_scroll_pagination package]
  return controller;
});

在ui文件中

class KeywordSearchResultPage extends StatefulHookConsumerWidget {
  const KeywordSearchResultPage({super.key, required this.keyword});
  final String keyword;

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _KeywordSearchResultPageState();
}
class _KeywordSearchResultPageState extends ConsumerState<KeywordSearchResultPage> {
  @override
  Widget build(BuildContext context) {
  /// [this is where i fetch paging controller data]
  final pagingController = ref.watch(keywordSearchResultNewsPaginationControllerProvider(widget.keyword));
  return Scaffold(
      backgroundColor: isDark ? AppColorPallete.baseBlackColor : AppColorPallete.brandBlueLightColor,
      appBar: AppBar(
        title: const Text("Search Results"),
        backgroundColor:
            isDarkModeOn(context) ? AppColorPallete.elevetedBlackColor : AppColorPallete.elevetedWhiteColor,
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(
            bottom: Radius.circular(20),
          ),
        ),
      ),
      body: pagingController.when(
      /// [PagedListView is coming from infinite scroll pagination package] 
      /// [this will handle all kind of thing when user reach end of the screen]
            data: (controller) => PagedListView<int, NewsPreviewModel>.separated(
             /// [passing the controller which i got from FutureProvider]
              pagingController: controller,
              builderDelegate: PagedChildBuilderDelegate<NewsPreviewModel>(
                firstPageProgressIndicatorBuilder: (context) => CustomLoader(),
                newPageProgressIndicatorBuilder: (context) =>  CustomLoader(),
                noItemsFoundIndicatorBuilder: (context) => NoDataFoundUi(msg: "No News Found for '${widget.keyword}'"),
                noMoreItemsIndicatorBuilder: (context) => Text("(You've reached the end of the news list.)"),
                firstPageErrorIndicatorBuilder: (context) => ErrorWidget(),
                
                /// [rendering card based on response data, this data is given by this itemBuilder function]
                itemBuilder: (context, item, index) => BigFeaturedNewsCard(news: item),
                ),
              ),
              separatorBuilder: (context, index) => kVerticalSpaceM,
            ),
            loading: () => PrimaryLoader(),
            error: (error, stackTrace) => ErrorWidget(),
          ), 
      );
  }

感谢您的阅读。有没有简单更好的方法来使用 Riverpod 来做到这一点?

flutter flutter-dependencies infinite-scroll riverpod
1个回答
1
投票

我假设您要求使用 Riverpod 进行分页。

我不喜欢使用任何库进行分页,最好使用“NotificationListener”小部件来处理特定列表并通过 api 调用管理页面

NotificationListener(
            onNotification: (ScrollNotification scrollInfo) {
              if (scrollInfo.metrics.pixels >=
                  scrollInfo.metrics.maxScrollExtent) {
                ref
                    .read(
                        yourProvider(searchText)
                            .notifier)
                    .fetchNextData();
              }
              return true;
            },
            child: Container()

我们也可以使用ScrollController

final ScrollController scrollController = ScrollController();


void _scrollListener() {
    if (scrollController.offset >= scrollController.position.maxScrollExtent &&
        !scrollController.position.outOfRange) {
      ref.read(yourProvider.notifier).fetchNextData();
    }
  }
  @override
  Widget build(BuildContext context) {
    final state = ref.watch(yourProvider);
    return state.when(data: (data) {
      scrollController.addListener(() {
        _scrollListener(data);
      });
      return ListView.builder(
          controller: scrollController, itemBuilder: (context, index) {});
    }, error: (error, stack) {
      return Container();
    }, loading: () {
      return CircularProgressIndicator(
        color: AppColors.white,
      );
    });
  }

并在提供程序中使用从 api 响应收到的页码管理 api 调用

if (state.value != null && currentPage < lastPage) { //do api call for next page  }

这里我使用了RiverPod,AsyncNotifierProvider。

我希望这对你有用。

© www.soinside.com 2019 - 2024. All rights reserved.