如何在Flutter中缓存Firebase数据?

问题描述 投票:0回答:3

在我的应用程序中,我使用 Firebase 中的数据构建对象列表。在 StreamBuilder 中,我检查快照是否有数据。如果没有,我将返回一个带有“正在加载...”的简单文本小部件。我的问题是,如果我转到应用程序中的另一个页面,然后返回,您会立即看到屏幕中间显示“正在加载...”,这有点令人恼火。我非常确定它正在从 Firebase 下载数据,并在每次我返回该页面时构建小部件。如果我不检查数据,它会给我一个我试图从 null 访问数据的数据。

有没有办法缓存已经下载的数据,如果Firebase中的数据没有变化,那么就使用缓存的数据?

这是我的代码的编辑版本:

class Schedule extends StatefulWidget implements AppPage {
  final Color color = Colors.green;
  @override
  _ScheduleState createState() => _ScheduleState();
}

class _ScheduleState extends State<Schedule> {
  List<Event> events;
  List<Event> dayEvents;
  int currentDay;
  Widget itemBuilder(BuildContext context, int index) {
    // Some Code
  }
  @override
  Widget build(BuildContext context) {
    return Center(
      child: StreamBuilder(
        stream: Firestore.instance.collection('events').snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Text("Loading...");
          }
          events = new List(snapshot.data.documents.length);
          for (int i = 0; i < snapshot.data.documents.length; i++) {
            DocumentSnapshot doc = snapshot.data.documents.elementAt(i);

            events[i] = Event(
              name: doc["name"],
              start: DateTime(
                doc["startTime"].year,
                doc["startTime"].month,
                doc["startTime"].day,
                doc["startTime"].hour,
                doc["startTime"].minute,
              ),
              end: DateTime(
                doc["endTime"].year,
                doc["endTime"].month,
                doc["endTime"].day,
                doc["endTime"].hour,
                doc["endTime"].minute,
              ),
              buildingDoc: doc["location"],
              type: doc["type"],
            );
          }
          events.sort((a, b) => a.start.compareTo(b.start));
          dayEvents = events.where((Event e) {
            return e.start.day == currentDay;
          }).toList();
          return ListView.builder(
            itemBuilder: itemBuilder,
            itemCount: dayEvents.length,
          );
        },
      ),
    );
  }
}
firebase caching flutter dart google-cloud-firestore
3个回答
14
投票

您可以使用以下代码来定义要从中检索数据的源。这将在本地缓存或服务器上搜索,而不是两者都搜索。它适用于所有

get()
参数,无论是搜索还是文档检索。

import 'package:cloud_firestore/cloud_firestore.dart';

FirebaseFirestore.instance.collection("collection").doc("doc").get(GetOptions(source: Source.cache))

要检查搜索缓存中是否有数据,您需要首先对缓存运行搜索,如果没有结果,则对服务器运行搜索。 我发现项目 firestore_collection 使用了一个简洁的扩展,可以大大简化这个过程。

import 'package:cloud_firestore/cloud_firestore.dart';

// https://github.com/furkansarihan/firestore_collection/blob/master/lib/firestore_document.dart
extension FirestoreDocumentExtension on DocumentReference {
  Future<DocumentSnapshot> getSavy() async {
    try {
      DocumentSnapshot ds = await this.get(GetOptions(source: Source.cache));
      if (!ds.exists) return this.get(GetOptions(source: Source.server));
      return ds;
    } catch (_) {
      return this.get(GetOptions(source: Source.server));
    }
  }
}

// https://github.com/furkansarihan/firestore_collection/blob/master/lib/firestore_query.dart
extension FirestoreQueryExtension on Query {
  Future<QuerySnapshot> getSavy() async {
    try {
      QuerySnapshot qs = await this.get(GetOptions(source: Source.cache));
      if (qs.docs.isEmpty) return this.get(GetOptions(source: Source.server));
      return qs;
    } catch (_) {
      return this.get(GetOptions(source: Source.server));
    }
  }

如果添加此代码,您只需将文档和查询的

.get()
命令更改为
.getSavy()
,它会首先自动尝试缓存,仅在本地找不到数据时才联系服务器。

FirebaseFirestore.instance.collection("collection").doc("doc").getSavy();

10
投票

要确定数据是来自 Firestore 的本地缓存还是来自网络,您可以执行以下操作:

          for (int i = 0; i < snapshot.data.documents.length; i++) {
            DocumentSnapshot doc = snapshot.data.documents.elementAt(i);
            print(doc.metadata.isFromCache ? "NOT FROM NETWORK" : "FROM NETWORK");

在您描述的情况下,当加载屏幕“不是来自网络”时,您可能仍然会看到加载屏幕。这是因为从本地缓存中获取它确实需要一些时间。很快,您将能够针对结果为空的情况请求查询的元数据

就像其他人建议的那样,您可以缓存结果,但您不会看到这一点。首先,您可以尝试使用以下方法将其缓存在小部件中:

  QuerySnapshot cache; //**

  @override
  Widget build(BuildContext context) {
    return Center(
      child: StreamBuilder(
        initialData: cache, //**
        stream: Firestore.instance.collection('events').snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Text("Loading...");
          }
          cache = snapshot.data; //**

这将使您的小部件记住数据。但是,如果这不能解决您的问题,您必须将其保存在其他位置,而不是在此小部件中。一种选择是使用

Provider
小部件将其存储在超出此特定小部件范围的变量中。

可能不相关,但将

Firestore.instance.collection('events').snapshots()
移至
initState()
,将对流的引用保存在私有字段中并使用它
StreamBuilder
也是一个好主意。否则,在每次 build() 时,您可能会创建一个新流。无论出于何种原因,您应该准备好应对每秒发生多次的
build()
调用。


2
投票

使用泛型

附加到上面@James Cameron的回答;我发现自己处于一种情况,所说的实现将我的类型转换从

withConverter
中删除了。因此,下面将泛型类型添加回函数中。

main.dart

import 'package:cloud_firestore/cloud_firestore.dart';

extension FirestoreDocumentExtension<T> on DocumentReference<T> {
  Future<DocumentSnapshot<T>> getCacheFirst() async {
    try {
      var ds = await get(const GetOptions(source: Source.cache));
      if (!ds.exists) return get(const GetOptions(source: Source.server));
      return ds;
    } catch (_) {
      return get(const GetOptions(source: Source.server));
    }
  }
}

extension FirestoreQueryExtension<T> on Query<T> {
  Future<QuerySnapshot<T>> getCacheFirst() async {
    try {
      var qs = await get(const GetOptions(source: Source.cache));
      if (qs.docs.isEmpty) return get(const GetOptions(source: Source.server));
      return qs;
    } catch (_) {
      return get(const GetOptions(source: Source.server));
    }
  }
}

use_case.dart
下面的实现无法使用 James 示例进行编译,如

DocumentSnapshot<Object?> is not a subset of DocumentSnapshot<UserModel>
。因此,通过重新添加通用参数,我们可以确保此扩展维护任何类型转换。

Future<DocumentSnapshot<UserModel>> userInfo() async {
  return await FirebaseFirestore.instance
      .doc("${path_to_user_model_doc}")
      .withConverter<UserModel>(
        fromFirestore: (snapshot, _) => UserModel.fromJson(snapshot.data()!),
        toFirestore: (userModel, _) => userModel.toJson(),
      )
      .getCacheFirst();
}

pubspec.yaml

environment:
  sdk: ">=2.17.1 <3.0.2"
dependencies:
  cloud_firestore: ^3.1.17
© www.soinside.com 2019 - 2024. All rights reserved.