我是初来乍到的,所以请容忍我。我有一个分页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/,它最后说的是“更新:”,但我无法理解它。
这是应用程序的入口点
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;
}
}这是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的构建方法调用如下:
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),
);
}
},
);
}}发布于 2018-12-18 22:10:35
我是初来乍到
欢迎!
首先,我想表达我对分页API的关注,因为可以在用户滚动列表时添加podcast,从而导致podcast丢失或显示两次。
说到这里,我想指出,您的问题措辞相当宽泛,所以--我将描述我自己的、固执己见的方法,说明我将如何在这个特定的用例中进行状态管理。很抱歉没有提供源代码,但是Flutter和BLoC模式是两个相对较新的东西,像分页加载之类的应用程序还需要探索。
我喜欢您选择的BLoC模式,尽管我不确定每次加载一些新的播客时,整个列表都需要重新构建。
而且,完全使用Sinks和Streams来完成事情的迂腐的集团化方式有时过于复杂。特别是如果没有连续的“数据流”,而只是数据传输的一个点,那么Future的工作做得相当好。这就是为什么我会在BLoC中生成一个方法,每次需要显示播客时都会调用该方法。它从页面上的播客数量或加载的概念中抽象出来--它每次只返回一个Future<Podcast>。
例如,考虑提供此方法的BLoC:
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小部件,如果podcast是null,则显示podcast或占位符。然后你就可以轻松地写:
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:我喜欢这个解决方案的特点是它本身具有灵活性和模块化,因为小部件本身非常“愚蠢”--缓存、加载等都发生在后台。利用这种灵活性,只要做一点点工作,您就可以轻松地实现以下功能:
_cache.clear()),播客将被自动重取。https://stackoverflow.com/questions/53729985
复制相似问题