Flutter:Streambuilder 导致 Firestore 上的读取过多
Posted
技术标签:
【中文标题】Flutter:Streambuilder 导致 Firestore 上的读取过多【英文标题】:Flutter: Streambuilder causing far too many reads on Firestore 【发布时间】:2020-09-25 07:08:57 【问题描述】:我正在尝试构建一个简单的引号 Flutter 应用程序,在其中显示引号列表并允许用户“喜欢”引号。为此,我正在使用 Streambuilder。我的问题是 Firestore 使用仪表板显示的读取次数非常多(每个用户几乎 300 次),即使我最多有 50 个引号。我有一种预感,我的代码中的某些内容导致 Streambuilder 多次触发(可能是用户“喜欢”一个报价),而且 Streambuilder 正在加载所有报价,而不仅仅是那些在用户视口中的报价。任何有关如何解决此问题以减少读取次数的帮助将不胜感激。
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:positivoapp/utilities.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:share/share.dart';
class QuotesScreen extends StatefulWidget
@override
QuotesScreenLayout createState() => QuotesScreenLayout();
class QuotesScreenLayout extends State<QuotesScreen>
List<String> quoteLikeList = new List<String>();
// Get Goals from SharedPrefs
@override
void initState()
super.initState();
getQuoteLikeList();
Future getQuoteLikeList() async
if (Globals.globalSharedPreferences.getString('quoteLikeList') == null)
print("No quotes liked yet");
return;
String quoteLikeListString =
Globals.globalSharedPreferences.getString('quoteLikeList');
quoteLikeList = List.from(json.decode(quoteLikeListString));
setState(() );
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.all(10.0),
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection(FireStoreCollections.QUOTES)
.orderBy('timestamp', descending: true)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot)
if (snapshot.hasError)
return new Text('Error: $snapshot.error');
switch (snapshot.connectionState)
case ConnectionState.waiting:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
new CircularProgressIndicator(),
new Text("Loading..."),
],
);
default:
print('Loading Quotes Stream');
return new ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document)
return new QuoteCard(
quote:
Quote.fromMap(document.data, document.documentID),
quoteLikeList: quoteLikeList,
);
).toList(),
);
,
)),
),
);
class QuoteCard extends StatelessWidget
Quote quote;
final _random = new Random();
List<String> quoteLikeList;
QuoteCard(@required this.quote, @required this.quoteLikeList);
@override
Widget build(BuildContext context)
bool isLiked = false;
String likeText = 'LIKE';
IconData icon = Icons.favorite_border;
if (quoteLikeList.contains(quote.quoteid))
icon = Icons.favorite;
likeText = 'LIKED';
isLiked = true;
return Center(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
constraints: new BoxConstraints.expand(
height: 350.0,
width: 400,
),
child: Stack(children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(0.25), BlendMode.darken),
image: AssetImage('images/$quote.imageName'),
fit: BoxFit.cover,
),
),
),
Center(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
quote.quote,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontFamily: 'bold',
fontWeight: FontWeight.bold,
color: Color.fromRGBO(255, 255, 255, 1)),
),
),
),
]),
),
Padding(
padding: EdgeInsets.fromLTRB(18, 10, 10, 0),
child: Text(
'Liked by $quote.numLikes happy people',
textAlign: TextAlign.left,
style: TextStyle(
fontFamily: 'bold',
fontWeight: FontWeight.bold,
color: Colors.black),
),
),
ButtonBar(
alignment: MainAxisAlignment.start,
children: <Widget>[
FlatButton(
child: UtilityFunctions.buildButtonRow(Colors.red, icon, likeText),
onPressed: () async
// User likes / dislikes this quote, do 3 things
// 1. Save like list to local storage
// 2. Update Like number in Firestore
// 3. Toggle isLiked
// 4. Setstate - No need
// Check if the quote went from liked to unlike or vice versa
if (isLiked == false)
// False -> True, increment, add to list
quoteLikeList.add(quote.quoteid);
Firestore.instance
.collection(FireStoreCollections.QUOTES)
.document(quote.documentID)
.updateData('likes': FieldValue.increment(1));
isLiked = true;
else
// True -> False, decrement, remove from list
Firestore.instance
.collection(FireStoreCollections.QUOTES)
.document(quote.documentID)
.updateData('likes': FieldValue.increment(-1));
quoteLikeList.remove(quote.quoteid);
isLiked = false;
// Write to local storage
String quoteLikeListJson = json.encode(quoteLikeList);
print('Size of write: $quoteLikeListJson.length');
Globals.globalSharedPreferences.setString(
'quoteLikeList', quoteLikeListJson);
// Guess setState(); will happen via StreamBuilder - Yes
// setState(() );
,
),
],
),
],
),
),
);
【问题讨论】:
你确定这不仅仅是让他的 Firestore 控制台在一个忙着写入的集合上打开吗?控制台成本读数也是如此。至于您的代码,您必须使用某种计数器来检测它,该计数器可以准确地告诉您它在做什么。 @DougStevenson:我每天只打开一次控制台,所以这可能不是问题。我看到只有 80 个用户的 24K 写入。是否有任何文档说明如何检测从 StreamBuilder 读取的 Firebase 文档? 您必须编写自己的代码来捕获和记录使用情况。 Firestore 不会为您执行此操作。 【参考方案1】:你的预感是正确的。由于您的 Streambuilder 在您的 Build 方法中,因此每次重新构建小部件树时都会在 Firestore 上进行读取。这比我here 解释得更好。
为防止这种情况发生,您应该在 initState 方法中监听 Firestore 流。这样它只会被调用一次。像这样:
class QuotesScreenLayout extends State<QuotesScreen>
List<String> quoteLikeList = new List<String>();
Stream yourStream;
// Get Goals from SharedPrefs
@override
void initState()
yourStream = Firestore.instance
.collection(FireStoreCollections.QUOTES)
.orderBy('timestamp', descending: true)
.snapshots();
super.initState();
getQuoteLikeList();
Future getQuoteLikeList() async
if (Globals.globalSharedPreferences.getString('quoteLikeList') == null)
print("No quotes liked yet");
return;
String quoteLikeListString =
Globals.globalSharedPreferences.getString('quoteLikeList');
quoteLikeList = List.from(json.decode(quoteLikeListString));
setState(() );
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.all(10.0),
child: StreamBuilder<QuerySnapshot>(
stream: yourStream,
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot)
【讨论】:
以上是关于Flutter:Streambuilder 导致 Firestore 上的读取过多的主要内容,如果未能解决你的问题,请参考以下文章
Flutter StreamBuilder 在加载时删除旧数据
Flutter 实现局部刷新 StreamBuilder 实例详解
Flutter 实现局部刷新 StreamBuilder 实例详解
FirebaseStorage + Flutter,streamBuilder?