【问题标题】:Cloud Firestore Keeps re-downloading documents - FlutterCloud Firestore 不断重新下载文档 - Flutter
【发布时间】:2019-07-27 13:52:02
【问题描述】:

这发生在我的主应用程序中 和 我用给定的代码实验室复制它: https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html?index=..%2F..index#10

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Baby Names',
     home: MyHomePage(),
   );
 }
}

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() {
   return _MyHomePageState();
 }
}

class _MyHomePageState extends State<MyHomePage> {
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: Text('Baby Name Votes')),
     body: _buildBody(context),
   );
 }

Widget _buildBody(BuildContext context) {
 return StreamBuilder<QuerySnapshot>(
   stream: Firestore.instance.collection('baby').snapshots(),
   builder: (context, snapshot) {
     if (!snapshot.hasData) return LinearProgressIndicator();

     return _buildList(context, snapshot.data.documents);
   },
 );
}

Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot){
   return ListView(
     padding: const EdgeInsets.only(top: 20.0),
     children: snapshot.map((data) => _buildListItem(context, data)).toList(),
   );
 }

 Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
  final record = Record.fromSnapshot(data);

   return Padding(
     key: ValueKey(record.name),
     padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
     child: Container(
       decoration: BoxDecoration(
         border: Border.all(color: Colors.grey),
         borderRadius: BorderRadius.circular(5.0),
       ),
       child: ListTile(
         title: Text(record.name),
         trailing: Text(record.votes.toString()),
         onTap: () => print(record),
       ),
     ),
   );
 }
}

class Record {
 final String name;
 final int votes;
 final DocumentReference reference;

 Record.fromMap(Map<String, dynamic> map, {this.reference})
     : assert(map['name'] != null),
       assert(map['votes'] != null),
       name = map['name'],
       votes = map['votes'];

 Record.fromSnapshot(DocumentSnapshot snapshot)
     : this.fromMap(snapshot.data, reference: snapshot.reference);

 @override
 String toString() => "Record<$name:$votes>";
}

我使用的唯一插件是 cloud_firestore 0.9.5+2。请您耐心等待这个测试过程。你不会马上看到问题。首先运行应用程序,设置此项目。您可以按照给定代码实验室中的说明进行操作。一旦在前端和后端设置了所有内容(在 firstore 上创建文档)。去吃午饭、吃晚饭、玩电子游戏或和朋友一起出去玩。 1小时后回来。运行该应用程序,您将因这些读取为新的而产生费用。再做一次,1小时回来,它会再次发生

如何在现实生活中复制它: 通过 codelabs 中的给定代码启动此应用程序。运行它,如果您将 4 个文档存储到 Firestore,它应该会导致 4 个文档读取。

重新启动它。不收取任何阅读费用。太好了!但不,真的没有。

我第二天醒来打开应用程序,我收取了 4 次阅读费用。 好吧,也许发生了一些神奇的事情。我立即重新启动它并且不会产生任何费用(太棒了!)。 1 小时后,我启动应用程序并收取 4 次读取费用,以显示完全没有更改的 4 个文档。

问题是,在应用启动时。它似乎正在从查询快照下载文档。没有对文件进行任何更改。这个流构建器之前已经运行了很多次。

离线模式(飞行模式),缓存数据显示没有问题。

例如,在我的主应用程序中,我有一个 photoUrl,并且在新的应用程序启动时,您可以看到它是从 firestore 加载的(意味着作为新文档下载,因此会产生 READ 费用)。我重新启动我的主应用程序,不收取任何费用,并且照片不刷新(太棒了!)。 1 小时后,我启动应用程序并为我检索到的每个文档收费(没有更改)。

这就是 Cloud Firestore 的行为方式吗? 根据我的阅读,它不应该像这样:(

【问题讨论】:

  • 您应该编辑问题以显示相关代码以及查询的实际执行方式,这样我们就可以就您的应用实际执行的操作达成一致,
  • 另请记住,Firestore 控制台账单会在您使用时读取。
  • 我知道它确实像我注意到的那样。我不再看控制台。我在使用我的应用程序 2 小时后查看。关闭我的应用程序并返回(2 小时后)到控制台查看控制台和 appengine 中的使用情况 我已经为此奋斗了 1 个月,但我认为我需要帮助
  • 也感谢您的快速回复。如果您使此代码正常工作并且后端按预期工作。请回复flutter通道、cloudstore插件版本、gradle设置等。我会尝试复制它,然后再次测试后回复

标签: firebase flutter google-cloud-firestore


【解决方案1】:

你不应该在build()做实际的工作,除了构建小部件树

而不是在构建中这样的代码

stream: Firestore.instance.collection('baby').snapshots(),

你应该使用

Stream<Snapshot> babyStream;
@override
void initState() {
  super.initState();
  babyStream = Firestore.instance.collection('baby').snapshots();
}

Widget _buildBody(BuildContext context) {
 return StreamBuilder<QuerySnapshot>(
   stream: babyStream,
   builder: (context, snapshot) {
     if (!snapshot.hasData) return LinearProgressIndicator();

     return _buildList(context, snapshot.data.documents);
   },
 );
}

FutureBuilder 文档没有明确提到它,但它是一样的

https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html

未来一定是更早获得的,例如期间 State.initState、State.didUpdateConfig 或 State.didChangeDependencies。期间不得创建 构造时调用 State.build 或 StatelessWidget.build 方法 未来建造者。如果未来与 FutureBuilder,那么每次重建 FutureBuilder 的父级时, 异步任务将重新启动。

【讨论】:

  • 感谢您的回答。我试过这个,但几个小时后测试仍然是同样的问题 Stream babyStream;我启动了应用程序,稍后我在 appengine 上查看,并为 4 个文档收取 4 次阅读费用。好的没问题。我重新启动应用程序。不收费(太棒了!)我在 1 小时后再次启动应用程序,我再次被收取 4 次阅读的费用。还是个问题。你能检查一下吗?在你身边运行这个。运行应用程序>等待1小时>再次运行它。检查它是否保持充电读数。感谢您的宝贵时间
  • 小部件可以在您离开和返回时重新创建,......状态丢失并再次获取数据。最好将值存储在小部件之外(全局变量、Redux、...),并且仅在尚未存储数据时从服务器获取。
  • 为什么 Firestore 缓存工作 30 分钟到 1 小时(即使在应用程序重新启动时)(然后突然内部缓存是多余的并强制 Firestore 重新下载所有文档作为新查询?不是检查后端应该由firestore插件处理?对不起,你给了我一个非常模糊的答案
  • 我不知道这样的时间限制,也不知道是什么导致了这种行为。我希望这是 Flutter 应用程序而不是 Firebase(插件或服务器)的行为,但又不知道这 1/2h 可能来自哪里。
  • 好的,我发布的这段代码在您的环境中可以正常工作吗?我只是想确认它是否适用于其他人,这样可以帮助我进行故障排除。谢谢
【解决方案2】:
Also, if the listener is disconnected for more than 30 minutes (for example, if the user goes offline), you will be charged for reads as if you had issued a brand-new query.

https://firebase.google.com/docs/firestore/pricing

我想我正面临着这个。 30 分钟(我测试了很多次),我可以启动我的应用程序并且不计入读取。等待(关闭应用程序 >)30~35 分钟后,我再次启动应用程序并为查询中的每个文档付费。

现在,如果我保持一个监听器打开(不关闭它的订阅),这应该避免这种情况并下载被修改的文档。

我试过这个:

  List<DocumentSnapshot> nameDocs = [];
  StreamSubscription nameSub;
  @override
  void initState() {
    super.initState();
    nameSub = Firestore.instance.collection('baby').snapshots().listen((data) {
      print('listener Fired at line 39 main.dart');
      data.documentChanges.forEach((x) {
        print('${x.type} this is the type line 40');
        if (x.type == DocumentChangeType.modified) {
          print('modified');
          nameDocs.removeAt(x.oldIndex);
          setState(() {
            nameDocs.add(x.document);
          });
        }
        if (x.type == DocumentChangeType.added) {
          print('added');
          setState(() {
            nameDocs.add(x.document);
          });
        }
        if (x.type == DocumentChangeType.removed) {
          print('removed');
          setState(() {
            nameDocs.removeAt(x.oldIndex);
          });
        }
        print(x.document.data['name']);
      });
    });
  }

我不取消订阅,因为我不想取消它。 在 31 分钟后重新访问应用后出现同样的问题

有没有办法让听众保持活力,我知道一些缺点可能是电池寿命,什么不是。我想在我的应用程序的某些部分保留实时监听器(例如在 Firestore 中不经常更改的文档,我想避免在我的 REAL 应用程序中重新下载此类文档)和我的其他部分将照常离开(收听并取消 dispose 方法)

【讨论】:

    猜你喜欢
    • 2020-07-02
    • 2021-07-31
    • 2021-01-06
    • 1970-01-01
    • 2019-06-21
    • 2020-09-26
    • 2018-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多