Flutter:StreamBuilder 导致在 Firestore 上进行了过多的读取

11

我正在尝试构建一个简单的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(() {});
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}


1
你确定这不是只是将Firestore控制台保持在一个有繁忙写入的集合上吗?控制台也会消耗读取次数。至于你的代码,你需要使用某种计数器来记录它正在做什么。 - Doug Stevenson
@DougStevenson:我一天只打开控制台一次,所以可能不是这个问题。我发现即使只有80个用户,也会有24K次写入。是否有关于如何从StreamBuilder记录Firebase文档读取的文档? - Varun
你需要编写自己的代码来捕获和记录使用情况。Firestore 不会为你完成这项工作。 - Doug Stevenson
1个回答

11
你的直觉是正确的。由于你的Streambuilder位于Build方法中,每次widget树重建时都会导致对Firestore进行读取。这个问题在这里得到了更好的解释:https://dev59.com/71QK5IYBdhLWcg3wNtTA
为了防止这种情况发生,你应该在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) {

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接