基于Riverpod(Future+Stream)的Appwrite实时数据查询优化问题
问题:优化Riverpod结合Appwrite的实时数据加载逻辑
背景
我在学习一份YouTube教程时,卡在了数据加载环节,觉得教程里的实现方案有优化空间,想找更简洁的写法。
目前从Appwrite获取数据的逻辑是教程作者推荐的:先通过FutureProvider(getTweetsProvider)拉取初始数据,再通过StreamProvider(getLatestTweetProvider)监听数据的实时变化。原代码如下:
Widget build(BuildContext context, WidgetRef ref) { return ref.watch(getTweetsProvider).when( data: (tweets) { return ref.watch(getLatestTweetProvider).when( data: (tweet) { if (tweet.events.contains( 'databases.*.collections.${AppwriteConstants.tweetsCollection}.documents.*.create')) { tweets.insert(0, TweetModel.fromMap(tweet.payload)); } return ListView.builder( itemCount: tweets.length, itemBuilder: (context, index) { final tweet = tweets.elementAt(index); return TweetCard(tweet: tweet); }, ); }, error: (error, stackTrace) => ErrorText(errorTxt: error.toString()), // 我不太喜欢这种写法... loading: () => ListView.builder( itemCount: tweets.length, itemBuilder: (context, index) { final tweet = tweets.elementAt(index); return TweetCard(tweet: tweet); }, ), ); }, error: (error, stackTrace) => ErrorText(errorTxt: error.toString()), loading: () => const Loader(), ); }
不满的点
这段代码里,必须在StreamProvider的loading分支里重复写一遍FutureProvider数据的渲染逻辑,非常冗余。
尝试过的思路
我想把ConsumerWidget改成ConsumerStatefulWidget,在initState里调用FutureProvider把初始数据存到本地变量(List tweets),只在build方法里监听StreamProvider的变化。但多次尝试都没成功。
之前在没有使用Provider的小型应用里,我实现过类似逻辑,代码如下:
List<Map<String, dynamic>> items = []; RealtimeSubscription? subscription; late final Client client; final databaseID = '...'; final itemsCollection = '...'; late final Databases database; void loadItems() async { try { final res = await database.listDocuments( collectionId: itemsCollection, databaseId: databaseID); setState(() { items = List<Document>.from(res.documents).map((e) => e.data).toList(); }); } on AppwriteException catch (e) { debugPrint(e.message); } } void subscribe() { final realtime = Realtime(client); subscription = realtime.subscribe( ['databases.$databaseID.collections.$itemsCollection.documents']); subscription!.stream.listen((data) { if (data.payload.isNotEmpty) { if (data.events .contains('databases.*.collections.*.documents.*.create')) { var item = data.payload; items.add(item); setState(() {}); } else if (data.events .contains('databases.*.collections.*.documents.*.delete')) { var item = data.payload; items.removeWhere((it) => it['\$id'] == item['\$id']); setState(() {}); } } }); } @override void initState() { client = Client(); client .setEndpoint('...') .setProject('...') .setSelfSigned(status: true); database = Databases(client); loadItems(); subscribe(); super.initState(); } @override void dispose() { subscription?.close(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Appwrite Realtime Demo'), ), body: ListView(children: [ ...items.map((item) => ListTile( title: Text(item['name']), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () => _removeItem(item['\$id']), ), )), ]), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () { // dialog to add new item showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Add new item'), content: TextField( controller: _nameController, ), actions: [ TextButton( child: const Text('Cancel'), onPressed: () => Navigator.of(context).pop(), ), TextButton( child: const Text('Add'), onPressed: () { // add new item final name = _nameController.text; if (name.isNotEmpty) { _nameController.clear(); _addItem(name); } Navigator.of(context).pop(); }, ), ], ), ); }, ), ); }
最终解决方案
后来我找到了更简洁的写法:用whenData替代第二个when,这样就不用重复处理StreamProvider的loading分支,代码更连贯清晰:
Widget build(BuildContext context, WidgetRef ref) { return ref.watch(getTweetsProvider).when( data: (tweets) { ref.watch(getLatestTweetProvider).whenData((tweet) { if (tweet.events.contains( 'databases.*.collections.${AppwriteConstants.tweetsCollection}.documents.*.create')) { tweets.insert(0, TweetModel.fromMap(tweet.payload)); } }); return ListView.builder( itemCount: tweets.length, itemBuilder: (context, index) { final tweet = tweets.elementAt(index); return TweetCard(tweet: tweet); }, ); }, error: (error, stackTrace) => ErrorText(errorTxt: error.toString()), loading: () => const Loader(), ); }
内容的提问来源于stack exchange,提问作者Adamsky
相关产品推荐
相关产品推荐

