善用 FetchMore GQL 实现无限滚动分页 ListView with Flutter

Posted

技术标签:

【中文标题】善用 FetchMore GQL 实现无限滚动分页 ListView with Flutter【英文标题】:Good use of FetchMore GQL for infinite scroll pagination ListView with Flutter 【发布时间】:2021-04-08 12:22:19 【问题描述】:

我正在尝试使用 graphql-flutter 库创建来自 GraphQL API 的元素的 ListView。

第一页的构建是好的。然后当我开始滚动时,结果会很好,我的 ListView 显示第二页,然后是第三页……正确。

问题是,从第二个页面加载开始,就出现了问题。 FetchMore 功能似乎在每个页面加载时构建多个请求。就像以前加载的所有页面都再次加载一样,越来越多。

奇怪的是,显示列表的结果对于几页都是正确的,然后列表突然从上一页循环回来,然后继续……

这是我的查询小部件:

Query(
  options: QueryOptions(
    document: gql(getHistory),
    variables: <String, dynamic>
      'pubkey': this.pubkey,
      'number': nRepositories,
      'cursor': null
    ,
  ),
  builder: (QueryResult result, refetch, FetchMore fetchMore) 
    if (result.isLoading && result.data == null) 
      return const Center(
        child: CircularProgressIndicator(),
      );
    

    if (result.hasException) 
      return Text('\nErrors: \n  ' + result.exception.toString());
    

    if (result.data == null && result.exception.toString() == null) 
      return const Text('Both data and errors are null');
    

    final List<dynamic> blockchainTX =
        (result.data['txsHistoryBc']['both']['edges'] as List<dynamic>);

    final Map pageInfo =
        result.data['txsHistoryBc']['both']['pageInfo'];

    final String fetchMoreCursor =
        pageInfo['endCursor'];

    FetchMoreOptions opts = FetchMoreOptions(
      variables: 'cursor': fetchMoreCursor,
      updateQuery: (previousResultData, fetchMoreResultData) 
        final List<dynamic> repos = [
          ...previousResultData['txsHistoryBc']['both']['edges']
              as List<dynamic>,
          ...fetchMoreResultData['txsHistoryBc']['both']['edges']
              as List<dynamic>
        ];

        fetchMoreResultData['txsHistoryBc']['both']['edges'] = repos;
        return fetchMoreResultData;
      ,
    );

    _scrollController
      ..addListener(() 
        if (_scrollController.position.pixels ==
            _scrollController.position.maxScrollExtent) 
          if (!result.isLoading) 
            print(
                "DEBUG fetchMoreCursor in scrollController: $fetchMoreCursor");
            fetchMore(opts);
          
        
      );

    print(
        "###### DEBUG Parse blockchainTX list. Cursor: $fetchMoreCursor ######");
    List _transBC = parseHistory(blockchainTX);

    return Expanded(
      child: HistoryListView(
          scrollController: _scrollController,
          transBC: _transBC,
          historyData: result),
    );
  ,
),

然后小部件HistoryListViewtransBC 数据构建列表。 这是DEBUG 行的打印结果(参见上面的代码),从应用程序开始只加载一页:

I/flutter ( 8745): ###### DEBUG Parse blockchainTX list. Cursor: 386237:8A98F83A120EF89FC65CF43BEE77068F3DA5734340B7987FA059D582A06934F8 ######
I/flutter ( 8745): DEBUG fetchMoreCursor in scrollController: 386237:8A98F83A120EF89FC65CF43BEE77068F3DA5734340B7987FA059D582A06934F8
I/flutter ( 8745): ###### DEBUG Parse blockchainTX list. Cursor: 386237:8A98F83A120EF89FC65CF43BEE77068F3DA5734340B7987FA059D582A06934F8 ######
I/flutter ( 8745): DEBUG fetchMoreCursor in scrollController: 386237:8A98F83A120EF89FC65CF43BEE77068F3DA5734340B7987FA059D582A06934F8
I/flutter ( 8745): ###### DEBUG Parse blockchainTX list. Cursor: 386237:8A98F83A120EF89FC65CF43BEE77068F3DA5734340B7987FA059D582A06934F8 ######
I/flutter ( 8745): ###### DEBUG Parse blockchainTX list. Cursor: 384695:4BF72317A538FB37F71C0A8D5CC36F319F87B7421260F128EFFF75B9A17C2CC7 ######
I/flutter ( 8745): ###### DEBUG Parse blockchainTX list. Cursor: 384695:4BF72317A538FB37F71C0A8D5CC36F319F87B7421260F128EFFF75B9A17C2CC7 ######

注意scrollController 函数执行了两次,第一个游标的值相同。 然后parseHistory 函数被执行了 3 次!我刚刚滚动到这里加载 1 页。

我真的不明白这种行为...... 我已经研究了一周了,如果这个库的开发者可以向我解释一下,那就太好了。

我正在使用 graphql_flutter 库的 4.0.0-beta.6 版本。


编辑:这是此测试的 UI 关联:

这里nRepository的值为3

【问题讨论】:

【参考方案1】:

这里的一个问题是您在build 函数中调用_scrollController.addListener(),因此每次视图重建时您都添加了一个侦听器。这就是为什么每次到达maxScrollExtent 时都会触发所有早期的fetchMore(opts) 调用。

您可以将您的 addListener 调用移至更合适的生命周期方法,例如 initState,但我实际上会推荐 using a NotificationListener&lt;ScrollUpdateNotification&gt;,它更具声明性,可能更适合您当前的代码。

与此无关,您可能需要检查一个空的“停止”光标以防止无限的空最后一页请求(尽管如果您的列表可以增长并且您的后端考虑到它,则可能不会)。

【讨论】:

谢谢你,我试着了解把我的addListener() 放在哪里打电话给fetchMore(opts),或者如何使用NotificationListener ... @poka 你找到解决办法了吗 @Shaon 是的,我不再使用addListener,而是使用NotificationListener 小部件;就像@micimize 一样,你可以在这里看到:git.duniter.org/clients/gecko/-/blob/master/lib/screens/… 一切正常。【参考方案2】:

我正在使用通知侦听器来捕获滚动到底部事件并且它正在工作。如果您想确保在获取更多内容后保持滚动位置,请务必在列表视图中添加键。

return NotificationListener(
  onNotification: (t) 
    if (t is ScrollEndNotification && _scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.7) 
       fetchMore(_fetchMoreOptions);
    
    return true;
  ,
  child: ListView.builder(
    key: const PageStorageKey<String>('uniqueString'),
    controller: _scrollController,
    ...
  ),
);

【讨论】:

以上是关于善用 FetchMore GQL 实现无限滚动分页 ListView with Flutter的主要内容,如果未能解决你的问题,请参考以下文章

TypeError:无法读取 Apollo 中未定义的属性“fetchMore”

Laravel 5 分页 + 无限滚动 jQuery

vue+element中的 InfiniteScroll 无限滚动 实现分页请求数据

在具有“合并”功能的 apollo 客户端分页配置中,即使调用 fetchMore,现有缓存数据也始终为空

js 实现无限加载分页(适合移动端)

React Native Shopify StoreFront API GraphQL API 无限加载产品 fetchMore 错误