首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >颤振:用动态ListView显示分页API中的内容

颤振:用动态ListView显示分页API中的内容
EN

Stack Overflow用户
提问于 2018-12-11 18:09:37
回答 1查看 8.1K关注 0票数 12

我是初来乍到的,所以请容忍我。我有一个分页API,这意味着在调用example.com?loaditems.php?page=0时,将加载前10个条目(podcast列表),而example.com?loaditems.php?page=1将从10项加载到20项,以此类推。我希望StreamBuilder先获取页面0,然后当列表到达底部时,它应该加载页面1并显示它。为了检查我是否达到了列表视图中的最后一项,我使用了ScrollController of ListView。

现在我使用的是StreamBuilder,ListView,InheritedWidget。我不确定我是否正确地实现了它,所以我要粘贴整个代码。我的问题是,这是正确的集团模式的做法吗?如果没有,那又是什么呢?我还看到了这篇文章:https://crossingthestreams.io/loading-paginated-data-with-list-views-in-flutter/,它最后说的是“更新:”,但我无法理解它。

这是应用程序的入口点

代码语言:javascript
复制
void main() => runApp(new MaterialApp(
title: "XYZ",
theme: ThemeData(fontFamily: 'Lato'),
home: PodcastsProvider( //This is InheritedWidget
  child: RecentPodcasts(), //This is the child of InheritedWidget
 ),
));

这是InheritedWidget PodcastsProvider:

代码语言:javascript
复制
class PodcastsProvider extends InheritedWidget{

    final PodcastsBloc bloc;  //THIS IS THE BLOC

    PodcastsProvider({Key key, Widget child})
    :   bloc = PodcastsBloc(),
    super(key: key, child: child);

    @override
    bool updateShouldNotify(InheritedWidget oldWidget) {
      return true;
    }

    static PodcastsBloc of(BuildContext context){
      return (context.inheritFromWidgetOfExactType(PodcastsProvider) as 
PodcastsProvider).bloc;
    }
}

这是Bloc

代码语言:javascript
复制
class PodcastsBloc{

    var _podcasts = PublishSubject<List<Podcast>>();

    Observable<List<Podcast>> get podcasts =>_podcasts.stream;

    getPodcasts(pageCount) async{
      NetworkProvider provider = NetworkProvider();
      var podcasts = await provider.getRecentPodcasts(pageCount);
     _podcasts.sink.add(podcasts);
    }

    despose(){
      _podcasts.close();
    }
}

下面是视图部分(InheritedWidget的子视图)

代码语言:javascript
复制
class RecentPodcasts extends StatefulWidget {
   @override
  _RecentPodcastsState createState() => _RecentPodcastsState();
}

class _RecentPodcastsState extends State<RecentPodcasts> {
   ScrollController controller = ScrollController();
   PodcastsBloc podcastsBloc;
   bool isLoading = false;
   List<Podcast> podcasts;

   @override
   void didChangeDependencies() {
     super.didChangeDependencies();
     podcastsBloc = PodcastsProvider.of(context);
     podcastsBloc.getPodcasts(null);
     controller.addListener((){
     if(controller.position.pixels == controller.position.maxScrollExtent && !isLoading){
       setState(() {
         isLoading = true;
         podcastsBloc.getPodcasts(podcasts[podcasts.length-1].id);
       });
      }
     }); //listerner ends
   }

最后, _RecentPodcastsState的构建方法调用如下:

代码语言:javascript
复制
Widget getRecentPodcastsList(PodcastsBloc podcastsBloc) {

return StreamBuilder(
  stream: podcastsBloc.podcasts,
  builder: (context, snapshot) {
    //isLoading = false;
    if (snapshot.hasData) {

      podcasts.addAll(snapshot.data); //THIS IS A PROBLEM, THIS GIVES ME AN ERROR: flutter: Tried calling: addAll(Instance(length:20) of '_GrowableList')

      return ListView.builder(
          scrollDirection: Axis.vertical,
          padding: EdgeInsets.zero,
          controller: controller,
          itemCount: podcasts.length,
          itemBuilder: (context, index) {
            return RecentPodcastListItem(podcasts[index]);
          });
    } else if (snapshot.hasError) {
      //SHOW ERROR TEXT
      return Text("Error!");
    } else {
      //LOADER GOES HERE
      return Text(
        "Loading...",
        style: TextStyle(color: Colors.white),
      );
    }
  },
);
}}
EN

回答 1

Stack Overflow用户

发布于 2018-12-18 22:10:35

我是初来乍到

欢迎!

首先,我想表达我对分页API的关注,因为可以在用户滚动列表时添加podcast,从而导致podcast丢失或显示两次。

说到这里,我想指出,您的问题措辞相当宽泛,所以--我将描述我自己的、固执己见的方法,说明我将如何在这个特定的用例中进行状态管理。很抱歉没有提供源代码,但是Flutter和BLoC模式是两个相对较新的东西,像分页加载之类的应用程序还需要探索。

我喜欢您选择的BLoC模式,尽管我不确定每次加载一些新的播客时,整个列表都需要重新构建。

而且,完全使用Sinks和Streams来完成事情的迂腐的集团化方式有时过于复杂。特别是如果没有连续的“数据流”,而只是数据传输的一个点,那么Future的工作做得相当好。这就是为什么我会在BLoC中生成一个方法,每次需要显示播客时都会调用该方法。它从页面上的播客数量或加载的概念中抽象出来--它每次只返回一个Future<Podcast>

例如,考虑提供此方法的BLoC:

代码语言:javascript
复制
final _cache = Map<int, Podcast>();
final _downloaders = Map<int, Future<List<Podcast>>>();

/// Downloads the podcast, if necessary.
Future<Podcast> getPodcast(int index) async {
  if (!_cache.containsKey(index)) {
    final page = index / 10;
    await _downloadPodcastsToCache(page);
  }
  if (!_cache.containsKey(index)) {
    // TODO: The download failed, so you should probably provide a more
    // meaningful error here.
    throw Error();
  }
  return _cache[index];
}

/// Downloads a page of podcasts to the cache or just waits if the page is
/// already being downloaded.
Future<void> _downloadPodcastsToCache(int page) async {
  if (!_downloaders.containsKey(page)) {
    _downloaders[page] = NetworkProvider().getRecentPodcasts(page);
    _downloaders[page].then((_) => _downloaders.remove(page));
  }
  final podcasts = await _downloaders[page];
  for (int i = 0; i < podcasts.length; i++) {
    _cache[10 * page + i] = podcasts[i];
  }
}

此方法为小部件层提供了一个非常简单的API。现在,让我们从小部件层的角度来看看它的外观。假设您有一个PodcastView小部件,如果podcastnull,则显示podcast或占位符。然后你就可以轻松地写:

代码语言:javascript
复制
Widget build(BuildContext context) {
  return ListView.builder(
    itemBuilder: (ctx, index) {
      return FutureBuilder(
        future: PodcastsProvider.of(ctx).getPodcast(index),
        builder: (BuildContext ctx, AsyncSnapshot<Podcast> snapshot) {
          if (snapshot.hasError) {
            return Text('An error occurred while downloading this podcast.');
          }
          return PodcastView(podcast: snapshot.data);
        }
      );
    }
  );
}

很简单对吧?

与链接中的解决方案相比,此解决方案的好处如下:

  • 用户不会失去他们的滚动速度,如果他们快速滚动,滚动视图永远不会“块”。
  • 如果用户滚动速度较快或网络延迟较高,则可能同时加载多个页面。
  • 播客寿命与小部件的生存期无关。如果您再次向下滚动和向上滚动,则尽管小部件是重新加载的,但播客不会重新加载。因为网络流量通常是一个瓶颈,这通常是一个值得做的权衡。请注意,这也可能是一个缺点,因为如果您有数万个播客,就需要担心缓存失效。

TL;DR:我喜欢这个解决方案的特点是它本身具有灵活性和模块化,因为小部件本身非常“愚蠢”--缓存、加载等都发生在后台。利用这种灵活性,只要做一点点工作,您就可以轻松地实现以下功能:

  • 您可以跳转到任意的id,从而只下载必要的小部件。
  • 如果你想做一个拉到重新加载的功能,只要抛出所有的缓存(_cache.clear()),播客将被自动重取。
票数 12
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53729985

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档