我是Flutter的新手所以请耐心等待.我有一个分页API,这意味着在调用时example.com?loaditems.php?page=0将加载前10个项目(播客列表),example.com?loaditems.php?page=1并将加载项目从10到20,依此类推.我希望StreamBuilder首先获取第0页,然后当列表到达底部时,它应该加载第1页并显示它.要检查我是否已到达列表视图中的最后一项,我使用的ScrollController是ListView.
现在我在bloc模式中使用StreamBuilder,ListView,InheritedWidget.我不确定我是否已正确实现它所以我将粘贴整个代码.我的问题是,这是正确的BLOC模式方式吗?如果不是那么它是什么?我还看到了这篇文章: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
),
));
Run Code Online (Sandbox Code Playgroud)
这是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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是集团
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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是视图部分(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
}
Run Code Online (Sandbox Code Playgroud)
最后,_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),
);
}
},
);
}}
Run Code Online (Sandbox Code Playgroud)
I am new to Flutter
Welcome!
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.
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.
I like your choice of BLoC pattern, although I'm not sure the entire list needs to rebuild every time some new podcasts loaded.
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.
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];
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
);
}
);
}
Run Code Online (Sandbox Code Playgroud)
Pretty simple, right?
Benefits of this solution compared to the one from your link:
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:
_cache.clear()),系统就会自动重新提取播客。| 归档时间: |
|
| 查看次数: |
2650 次 |
| 最近记录: |