Flutter:Streambuilder 导致 Firestore 上的读取过多

标签 flutter google-cloud-firestore stream-builder

我正在尝试构建一个简单的引号 Flutter 应用程序,在其中显示引号列表并允许用户“喜欢”引号。为此,我正在使用 Streambuilder。我的问题是 Firestore 使用仪表板显示的读取次数非常多(每个用户几乎 300 次),即使我最多有 50 个引号。我有一种预感,我的代码中的某些内容导致 Streambuilder 多次触发(可能是用户“喜欢”一个报价),而且 Streambuilder 正在加载所有报价,而不仅仅是那些在用户视口(viewport)中的报价。任何有关如何解决此问题以减少读取次数的帮助将不胜感激。

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(() {});
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

最佳答案

你的预感是正确的。由于您的 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 上的读取过多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62226143/

相关文章:

android - 应用程序在启动时崩溃,没有错误消息

Flutter/Dart 返回 '_Future<String>' 的实例而不是实际的字符串值

swift - Firestore 按时间排序但按 ID 排序

Flutter:StreamBuilder 快照——无数据

dart - StreamBuilder 在调用 StreamController.sink.add 时没有反应

firebase - flutter :显示在一段时间内调用了null的getter文档

flutter - 使用工作表中的 ScrollView 向下滚动 flutter 模态底部工作表

java - FireStore 数据未显示正在访问隐藏方法 Lsun/misc/Unsafe;->putLong(Ljava/lang/Object;JJ)V(灰名单、链接、允许)

ios - 如何获取当前登录用户关注的用户帖子

firebase - Flutter:StreamBuilder中的项目(使用firebase实时数据库)随机排序