问题描述
我正在尝试构建一个简单的报价Flutter应用程序,在其中显示报价列表并允许用户喜欢"报价.我正在为此使用Streambuilder.我的问题是,即使我最多有50个引号,Firestore的使用情况仪表板仍显示大量读取(每个用户近300个).我有一种预感,我代码中的某些内容导致Streambuilder多次触发(也许用户喜欢"引号),并且Streambuilder正在加载所有引号,而不是仅加载用户视口中的引号.对于如何解决此问题以减少读取次数的任何帮助,将不胜感激.
I am trying to build a simple quotes Flutter app, where I show a list of quotes and allow users to 'like' the quotes. I am using the Streambuilder for that. My problem is that the Firestore usage dashboard shows a very high number of reads (almost 300 per user), even though I have 50 quotes at max. I have a hunch that something in my code is causing Streambuilder to trigger multiple times (maybe the user 'liking' a quote) and also the Streambuilder is loading ALL the quotes instead of only those that are in the user's viewport. Any help on how to fix this to reduce the number of reads would be appreciated.
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(() {});
},
),
],
),
],
),
),
);
}
}
推荐答案
这为我修复了此问题:在定义流的位置,请确保将查询限制为一定数量,这将是检索(读取)的文档数.
This fixed it for me:Where you define your stream, make sure to limit the query to a certain number, and that will be the number of documents retrieved (read).
Firestore.instance.collection('collection').orderBy('name').limit(ItemCount + 10).snapshots()
假设您希望在用户滚动时逐步检索文档,请考虑将滚动控制器添加到列表视图中,并在滚动控制器达到最大范围时,增加查询限制(ItemCount)(您还将希望给自己一点增加的余地,让Flutter有时间在滚动时渲染新的小部件,因此在我的情况下为+10).这不是一个完美的解决方案,因为该查询将在您每次递增时仍然运行(因此读取次数将是10,然后是10 + 10,然后是10 + 10 + 10,依此类推,但是肯定已经有了改进,而不是一次所有文档) ).
Assuming you'd like to retrieve documents incrementally as a user scrolls, consider adding a scroll controller to your list view, and when the scroll controller is at it's max extent, increment the query limit (ItemCount)(you'll also want to give yourself a little leeway with the increment to give Flutter time to render the new widgets as you scroll, hence the +10 in my case). Not a perfect solution, as that query will still be run every time you increment (so reads will be 10, then 10+10, then 10+10+10, and so on, but definitely an improvement already instead of all documents at once).
设置Google Cloud Dashboard来跟踪文档读取(由于某些原因,它比firebase控制台还最新)也很有用,如果您违反了文档读取的特定阈值,则设置警报.注意在添加前面提到的限制之前和之后,下面的区别:
It's also useful to set up a Google Cloud Dashboard to track document reads (more up to date than firebase console for some reason), and set up alerts if you breach a certain threshold of document reads. Notice the difference below before and after adding the limit mentioned previously:
P.S.一段时间以来,我还试图弄清楚为什么我的StreamBuilders进行了如此多的读取.
P.S. For awhile I was also trying to figure out why my StreamBuilders made so many reads.
这篇关于Flutter:Streambuilder导致Firestore读取过多的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!