Flutter 颤振:使用动态ListView显示分页API中的内容
我是新手,所以请容忍我。我有一个分页API,这意味着调用Flutter 颤振:使用动态ListView显示分页API中的内容,flutter,Flutter,我是新手,所以请容忍我。我有一个分页API,这意味着调用example.com?loaditems.php?page=0将加载前10个项目(播客列表),而example.com?loaditems.php?page=1将加载10到20个项目,依此类推。 我希望StreamBuilder首先获取第0页,然后当列表到达底部时,它应该加载第1页并显示它。要检查是否已到达listview中的最后一项,我正在使用listview的ScrollController 现在,我在bloc模式中使用StreamB
example.com?loaditems.php?page=0将加载前10个项目(播客列表),而example.com?loaditems.php?page=1将加载10到20个项目,依此类推。
我希望StreamBuilder首先获取第0页,然后当列表到达底部时,它应该加载第1页并显示它。要检查是否已到达listview中的最后一项,我正在使用listview的ScrollController
现在,我在bloc模式中使用StreamBuilder、ListView和InheritedWidget。我不确定我是否正确实现了它,所以我将粘贴整个代码。
我的问题是,这是正确的集团模式吗?如果不是,那是什么?
我还看到了这篇文章:
最后它写着“更新:”但我不太明白
下面是应用程序的入口点:
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播客提供者:
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();
}
}
我是个新手
欢迎光临
首先,我想表达我对分页API的担忧,因为用户滚动列表时可以添加播客,导致播客丢失或显示两次
在此,我想指出,您的问题措辞相当宽泛,因此我将描述我自己的、自以为是的方法,说明我将如何在这个特定用例中进行状态管理。
很抱歉没有提供源代码,但颤振和BLoC模式是两个相对较新的东西,分页加载等应用程序仍然需要探索
我喜欢你选择的集团模式,尽管我不确定每次加载新播客时是否需要重新生成整个列表
此外,完全使用Sink
s和Stream
s的迂腐的整体式做事方式有时过于复杂。
特别是如果没有连续的“数据流”,而只是一个数据传输点,Future
s会很好地完成这项工作。
这就是为什么我会在集群中生成一个方法,每次需要显示播客时都会调用该方法。它根据页面上播客的数量或加载的概念进行抽象,每次只返回一个Future
例如,考虑一个提供这种方法的组:
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];
}
}
很简单,对吧
与链接中的解决方案相比,此解决方案的优势:
- 如果用户快速滚动,他们不会失去滚动速度,滚动视图不会“阻塞”
- 如果用户滚动速度快或网络延迟高,则可能同时加载多个页面
- 播客的寿命与小部件的寿命无关。如果您再次向下和向上滚动,播客不会被重新加载,尽管小部件会被重新加载。因为网络流量通常是一个瓶颈,所以这通常是一个值得做的权衡。请注意,这也可能是一个缺点,因为如果有成千上万的播客,您需要担心缓存失效
TL;DR:我喜欢该解决方案的一点是,它具有固有的灵活性和模块化,因为小部件本身非常“愚蠢”——缓存、加载等。所有这些都发生在后台。
利用这种灵活性,只需稍加努力,您就可以轻松实现以下功能:
- 您可以跳转到任意id,从而只下载必要的小部件
- 如果您想使用拉式重新加载功能,只需扔掉所有缓存(
\u cache.clear()
)即可自动重新获取播客
这是非常广泛的。你想完成什么?非常感谢你抽出时间详细回答。我会尝试你提供的解决方案,并会回来。考虑使用SpReTreMap代替地图。看见
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),
);
}
},
);
}}
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];
}
}
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);
}
);
}
);
}