Flutter:使用动态ListView显示来自分页API的内容 [英] Flutter: Display content from paginated API with dynamic ListView

查看:118
本文介绍了Flutter:使用动态ListView显示来自分页API的内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Flutter的新手,所以请多多包涵.我有一个分页的API,这意味着调用example.com?loaditems.php?page=0时将加载前10个项目(播客列表),而example.com?loaditems.php?page=1则将加载10至20个项目,依此类推. 我希望StreamBuilder首先获取页面0,然后当列表到达底部时,它应该加载页面1并显示它.要检查我是否已到达列表视图的最后一项,我正在使用列表视图的ScrollController.

I am new to Flutter so please bear with me. I have a paginated API that means on calling example.com?loaditems.php?page=0 will load first 10 items(podcasts list) and example.com?loaditems.php?page=1 will load items from 10 to 20 and so on. I want StreamBuilder to fetch page 0 first then when the list reaches the bottom it should load page 1 and show it. To check if I have reached the last item in listview I am using ScrollController of ListView.

现在我正在bloc模式中使用StreamBuilder,ListView,InheritedWidget.我不确定我是否正确实现了它,所以我要粘贴整个代码. 我的问题是,这是正确的BLOC模式方法吗?如果没有,那是什么? 我也看到了这篇文章: https://crossingthestreams. io/在页面中加载带有列表视图的分页数据/ 最后,它说更新:",但我听不懂.

Now I am using StreamBuilder, ListView, InheritedWidget in bloc pattern. I am not sure if I have implemented it correctly so I am gonna paste the entire code. My question is, is this is the correct BLOC pattern way to do it? If not then what is it? I also came across with this article: https://crossingthestreams.io/loading-paginated-data-with-list-views-in-flutter/ In the end it says "Update:" but I could not understand it much.

此处是应用的入口点:

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:

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;
    }
}

这是集团

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的子项)

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的 build方法调用:

Finally, build method of _RecentPodcastsState calls this:

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),
      );
    }
  },
);
}}

推荐答案

我是Flutter的新手

I am new to Flutter

欢迎!

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

First of all, I want to express my concern against paginated APIs, since podcasts can be added while the user scrolls the list, resulting in podcasts missing or being displayed twice.

我想指出的是,您的问题用词很笼统,所以我将针对如何在此特定用例中进行状态管理描述自己的,有见地的方法. . 抱歉,没有提供源代码,但是Flutter和BLoC模式是两个相对较新的东西,仍然需要探索分页加载之类的应用程序.

Having that out of the way, I'd like to point out that your question is quite broadly phrased, so I'll describe my own, opinionated approach on how I would do state management in this particular use case. Sorry for not providing sources, but Flutter and BLoC pattern are two relatively new things, and applications like paginated loading still need to be explored.

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

I like your choice of BLoC pattern, although I'm not sure the entire list needs to rebuild every time some new podcasts loaded.

此外,完全使用SinkStream的简单的BLoC-y处理方式有时过于复杂. 尤其是如果没有连续的数据流"而是只有一个数据传输点,则Future可以很好地完成工作. 这就是为什么我会在BLoC中生成一个方法,该方法在每次需要显示播客时都会被调用.它从页面上的播客数量或加载的概念中抽象出来-每次都简单地返回Future<Podcast>.

Also, the pedantically BLoC-y way of doing things entirely with Sinks and Streams is sometimes overly complex. Especially if there is no continual "stream of data" but rather just a single point of data transimission, Futures do the job quite well. That's why I'd generate a method in the BLoC that gets called every time a podcast needs to be displayed. It abstracts from the number of podcasts on a page or the concept of loading - it simply returns a Future<Podcast> every time.

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

For example, consider a BLoC providing this method:

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或占位符.然后,您可以轻松地编写:

This method provides a very simple API to your widget layer. So now, let's see how it look from the widget layer point of view. Assume, you have a PodcastView widget that displays a Podcast or a placeholder if podcast is null. Then you could easily write:

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);
        }
      );
    }
  );
}

很简单,对吧?

与您的链接相比,该解决方案的优点:

Benefits of this solution compared to the one from your link:

  • 如果用户快速滚动,则不会失去滚动速度,滚动视图永远不会阻塞".
  • 如果用户快速滚动或网络延迟很高,则可能会同时加载多个页面.
  • 播客的寿命独立于小部件的寿命.如果您上下滚动,尽管小部件已加载,但不会重新加载播客.因为网络流量通常是瓶颈,所以这通常是一个值得权衡的事情.请注意,这也可能是不利的一面,因为如果您拥有成千上万的播客,则需要担心缓存失效.

TL; DR :我喜欢该解决方案的是它固有的灵活性和模块化,因为小部件本身非常笨拙"-缓存,加载等都在后台进行. 通过一点点的工作,利用这种灵活性,您可以轻松实现以下功能:

TL;DR: What I like about that solution is that it's inherently flexible and modular because the widgets themselves are quite "dumb" - caching, loading etc. all happens in the background. Making use of that flexibility, with a little bit of work, you could easily achieve these features:

  • 您可以跳转到任意ID,从而仅下载必要的小部件.
  • 如果您要使用即插即用功能,只需丢弃所有缓存(_cache.clear()),播客将自动重新获取.
  • You could jump to an arbitrary id, causing only the necessary widgets to be downloaded.
  • If you want to make a pull-to-reload functionality, just throw out all the cache (_cache.clear()) and the podcasts will be re-fetched automatically.

这篇关于Flutter:使用动态ListView显示来自分页API的内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆