firebase创建数据库_生存指南:如何从Firebase实时数据库迁移到Cloud Firestore

相诚
2023-12-01

firebase创建数据库

by Alex Saveau

通过Alex Saveau

生存指南:如何从Firebase实时数据库迁移到Cloud Firestore (Survival guide: how to migrate from the Firebase Realtime Database to Cloud Firestore)

Ever since Google’s new mobile SDKs were announced two years ago at I/O, the Firebase empire has been ever-expanding. It now supports more and more features, such as Cloud Functions, phone authentication, and performance monitoring. However, one SDK that hasn’t changed much is the Firebase Realtime Database (RTDB).

自两年前在I / O上宣布 Google的新移动SDK以来,Firebase帝国一直在不断扩展。 现在,它支持越来越多的功能,例如云功能,电话身份验证和性能监控。 但是,Firebase实时数据库(RTDB)是一个变化不大的SDK。

The RTDB hasn’t received any major updates — and not because it was a perfect API. Far from it. If you’ve read about Pier Bover’s experience, or have used the Firebase RTDB yourself, these problems might sound familiar:

RTDB尚未收到任何重大更新-并不是因为它是一个完美的API。 离得很远。 如果您已经阅读了Pier Bover的经验 ,或者自己使用了Firebase RTDB,这些问题可能听起来很熟悉:

No way to query your data properly […] and dumb data modelling.
无法正确查询您的数据[…]和哑数据建模。

So what’s next? How is Google going to solve these limitations? Instead of releasing a version 4.0 of the RTDB, which would be messy and painful for everyone, Google is using what it learned from the Firebase Realtime Database’s faults. And they’re completely redesigning and rewriting it from scratch into a new database: Cloud Firestore.

下一个是什么? Google如何解决这些限制? Google并未发布RTDB 4.0版(对每个人来说都是一件麻烦事和痛苦的事),而是使用从Firebase实时数据库的故障中学到的信息。 他们正在完全重新设计并将其从头重写到新的数据库:Cloud Firestore。

The RTDB isn’t going away — that would cause a huge crisis. But going forward, Cloud Firestore will receive most of the attention and love.

RTDB并没有消失,那将导致巨大的危机。 但是,展望未来,Cloud Firestore将获得大多数关注和喜爱。

This article is going to delve deep into Google’s long-awaited database revamp, mostly from an Android RTDB developer’s point of view. In addition, the article is intended as a replacement for hours of pouring over documentation to build a mental model of the new SDK.

本文将主要从Android RTDB开发人员的角度深入研究Google期待已久的数据库改造。 此外,本文旨在替代为建立新SDK的思维模型而花费大量时间的文档。

背景 (Background)

Unless you’ve just recently joined the Firebase party, you’ve probably heard of the Google Cloud Platform (GCP). Except for the RTDB, all other Firebase server products, like Cloud Functions and Firebase Storage, are usually rebrands of existing GCP solutions with extra bells and whistles — plus Firebase integration and branding.

除非您最近刚刚参加Firebase派对,否则您可能听说过Google Cloud Platform(GCP)。 除RTDB之外,其他所有Firebase服务器产品(例如Cloud Functions和Firebase Storage)通常都是现有GCP解决方案的品牌重塑,并带有额外的功能-以及Firebase集成和品牌。

However, the RTDB was ported over from the pre-Google Firebase. It turns out that the database actually used to be a chat service. They only decided to strip out the UI and turn it into an SDK after the company shifted its focus. With the never-ending swarm of chat app samples, you’d think they’re still a little bit nostalgic.

但是,RTDB是从Google之前的Firebase移植过来的。 事实证明,该数据库实际上曾经是聊天服务。 他们只是决定在公司转移工作重点后剥离UI并将其转换为SDK。 随着聊天应用程序样本的不断涌现,您可能会觉得它们仍然有些怀旧。

On the other hand, Cloud Firestore is built off GCP’s Google Cloud Datastore, a NoSQL database with near-infinite scalability and powerful querying abilities. The Firebase-branded Cloud Datastore adds the expected realtime capabilities and, of course, integration with other Firebase services such as Authentication and Cloud Functions.

另一方面,Cloud Firestore是在GCP的Google Cloud Datastore(基于NoSQL数据库,具有几乎无限的可扩展性和强大的查询功能)的基础上构建的。 Firebase品牌的Cloud Datastore增加了预期的实时功能,并且当然与其他Firebase服务(例如身份验证和云功能)集成。

For database fans, Cloud Datastore is a multi-region, synchronously-replicated database that uses ACID transactions. This means that once Google says your write is committed, a dinosaur-age meteor could take out a region and your data would still be safe and sound, ready to be queried.

对于数据库迷来说,Cloud Datastore是一个使用ACID事务的多区域同步复制数据库。 这意味着,一旦Google说您的写操作已经完成,那么恐龙时代的流星可能会占据一个区域,并且您的数据仍然是安全可靠的,随时可以查询。

Now I’m not saying we humans would fare so well…but at least your data would still be rock solid! Oh, and it uses atomic clocks — now if that isn’t cool, I don’t know what is!

现在,我并不是说人类会过得很好……但至少您的数据仍然坚如磐石! 哦,它使用原子钟-现在,如果那不酷,我不知道这是什么!

Now that you have a basic understanding of why Google decided to create a completely new Firebase branded database and where it came from, let’s get started!

现在,您已经对Google为何决定创建一个全新的Firebase品牌数据库以及数据库的来源有了基本的了解,让我们开始吧!

For the rest of this article, I’ll be using examples from apps I’ve built (so no chat app samples! ?). Specifically, I’ll be using examples from Robot Scouter, an app to help FIRST Robotics Competition teams make data-driven decisions during competitions.

对于本文的其余部分,我将使用自己构建的应用程序中的示例(因此,没有聊天应用程序示例!?)。 具体来说,我将使用R obot Scouter的示例该应用程序可帮助F IRST机器人大赛团队在比赛中做出以数据为依据的决策。

The app’s basic purpose is to allow users to collect data about other teams in units called scouts. These scouts can be based off customizable templates. Scouts and templates are composed of metrics, which are different types of data a user can collect. Templates are standalone objects, but scouts are implicitly owned by a team.

该应用程序的基本目的是允许用户以名为“ 侦察兵”的单位收集有关其他团队的数据。 这些侦察兵可以基于可自定义的模板 侦察员和模板由度量构成,这些度量是用户可以收集的不同类型的数据。 模板是独立的对象,但是侦察兵是团队隐式拥有的。

Individual teams and templates can be shared with other users, but the scouts will follow a team around wherever it goes since the team owns them.

各个团队和模板可以与其他用户共享,但由于团队拥有球探,因此侦察人员将随队随处走动。

数据结构 (Data structures)

Let’s start by looking at what the Robot Scouter data structure looked like with the RTDB. Take a deep breath, there’s a lot to scroll through:

让我们先来看一下RTDB的Robot Scouter数据结构。 深呼吸,有很多内容可以滚动:

All we really wanted was a teams, templates, and users collection. Instead, we had to denormalize our data to accommodate for the RTDB’s deep-by-default queries. Remember, if we query a node with the RTDB, we also get all child nodes.

我们真正想要的只是一个teamstemplatesusers集合。 取而代之的是,我们必须对数据进行非规范化处理以适应RTDB的默认默认查询。 请记住,如果我们使用RTDB查询节点,那么我们还将获得所有子节点。

Now let’s take a look at the equivalent Cloud Firestore data structure:

现在,让我们看一下等效的Cloud Firestore数据结构:

The Cloud Firestore data structure is easier to understand and much shorter than the RTDB structure. This is thanks to nested refs versus being forced to denormalize everything in the RTDB.

Cloud Firestore数据结构比RTDB结构更易于理解,并且要短得多。 这要归功于嵌套的引用,而不是被迫对RTDB中的所有内容进行非规范化。

数据结构差异 (Data structure differences)

The first major difference you’ll notice is the lack of denormalization. In the Cloud Firestore data structure, we declare our teams ref and put each team directly inside that ref instead of flattening it out. Similarly, we’ve merged the template-indices directly into the templates ref.

您会注意到的第一个主要区别是缺少非规范化。 在Cloud Firestore数据结构中 ,我们声明我们的teams ref,并将每个团队直接放在该ref中,而不是将其展平。 同样,我们将template-indices直接合并到templates引用中。

You might have also noticed that our scouts are now directly placed inside a team instead of being in a separate indices ref. Your first reaction might have been “Wait, you’re going to waste all the user’s data! Don’t do that!” This is the beauty of Cloud Firestore: queries are “shallow” by default.

您可能还注意到,我们的scouts现在直接放置在团队中,而不是放在单独的索引引用中。 您的第一个React可能是“等等,您将浪费所有用户的数据! 不要那样做!” 这就是Cloud Firestore的优点: 默认情况下,查询是“浅”的

I put the word shallow in quotes because technically, you could nest a huge amount of data in your documents. But we’ll talk about why you shouldn’t do that later. Wait a sec, what’s a document? Cloud Firestore has two basic building blocks: collections and documents.

我将“ ”一词用引号引起来,因为从技术上讲,您可以在文档中嵌套大量数据。 但是,我们将讨论为什么您以后不应该这样做。 请稍等,什么是文件? Cloud Firestore有两个基本构建块: 集合文档

Collections in Firestore are the equivalent of a ref in the RTDB with a huge list of child nodes that each contain objects. If you scroll back up to the Firestore data structure, you’ll notice that teams, templates, and users are all collections. They each contain a whole bunch of objects — and in Firestore, these objects are called documents.

Firestore中的集合相当于RTDB中的ref,其中包含大量的每个包含对象的子节点列表。 如果您回滚到Firestore数据结构,您会注意到teamstemplatesusers都是集合。 它们每个都包含一堆对象-在Firestore中,这些对象称为文档。

Documents would be your conventional object node in the RTDB. However, in Firestore they’re a little special: documents are explicitly owned by a collection. In the RTDB, you could put pretty much anything anywhere. Cloud Firestore brings in a little sanity and uses an alternating pattern of collections and documents which looks a little like this: collection1/document1/collection2/document2/....

文档将是RTDB中的常规对象节点。 但是,在Firestore中,它们有一些特殊之处:文档由集合明确拥有。 在RTDB中,您几乎可以在任何地方放置任何东西。 Cloud Firestore带来了一些理智,并使用了类似的集合和文档交替模式: collection1/document1/collection2/document2/...

While this pattern could feel constraining at first, I’ve found it helpful in forcing me to design a neatly organized data structure. You’ll notice that my scouts collection now properly resides within a team document. I only had to split it out in the RTDB so that my users wouldn’t have to download all their scouts when looking at a team. In Cloud Firestore, teams have explicit ownership of a set of scouts without having to download them when loading the team.

虽然这种模式起初可能会让人感到束缚,但我发现它有助于迫使我设计一个整齐有序的数据结构。 您会注意到,我的scouts收藏现已正确存在于团队文档中。 我只需要在RTDB中将其拆分即可,这样我的用户在查看团队时就不必下载所有的侦察员。 在Cloud Firestore中,团队拥有一组侦察员的显式所有权,而无需在加载团队时下载它们。

有关文件的更多信息 (A little more on documents)

In the RTDB, you had a free-flowing model with 3 core data types: booleans, strings, and numbers. Things like lists and maps were either an afterthought or just part of how you queried for data in the RTDB.

在RTDB中,您有一个自由流动的模型,其中包含3种核心数据类型:布尔值,字符串和数字。 列表和地图之类的内容不是您想做的,还是只是您在RTDB中查询数据的一部分。

On the other hand, with Firestore, you have a very clear cut structure: collections contain documents, and documents contain fields and links to one or more subcollections.

另一方面,使用Firestore,您将拥有一个非常清晰的结构:集合包含文档,而文档则包含字段和指向一个或多个子集合的 链接

Subcollection is just a fancy term for another list of objects owned by a document — except you won’t get that list when querying the document. This is because documents don’t technically contain subcollections. They just link to them. Hence why we can put our scouts collection inside the team document — or link to it, if you will.

子集合只是文档所拥有的另一个对象列表的一个奇特名词–除非在查询文档时不会得到该列表。 这是因为文档在技术上不包含子集合。 他们只是链接到他们。 因此,为什么我们可以将scouts集合放入团队文档中-或链接到该文档(如果您愿意)。

On top of containing subcollections, documents in Firestore support a vast array of data types with more on the horizon. For now, here are the supported types:

除了包含子集合之外,Firestore中的文档还支持各种各样的数据类型,并且还有更多的数据类型 。 目前,这是受支持的类型:

  • Boolean

    布尔型
  • String

  • Number

  • Raw bytes (if that’s your style ?)

    原始字节(如果这是您的风格?)
  • Dates and time

    日期和时间
  • Geo points

    地理位置
  • Refs

    参考
  • Arrays and maps

    数组和地图
  • Null!

    空值!

Yes, null is now an explicitly defined data type in Cloud Firestore. If you set a document equal to a Java object whose getter returns null, the field will still show up in the Firebase Console with the null data type.

是的, null现在是Cloud Firestore中明确定义的数据类型。 如果您将文档设置为等于其getter返回null的Java对象,则该字段仍将以null数据类型显示在Firebase控制台中。

Well ok, so what? Adding the null data type makes field deletion explicit. In the RTDB, setting something to null is the same as deleting it. But in Firestore, to delete a field you must set its value to FieldValue.delete(). On a similar note, ServerValue.TIMESTAMP has become FieldValue.serverTimestamp().

好吧,那又如何呢? 添加null数据类型使字段删除变得明确。 在RTDB中,将某些内容设置为null等同于将其删除。 但是在Firestore中,要删除字段,必须将其值设置为FieldValue.delete() 。 类似地, ServerValue.TIMESTAMP已成为FieldValue.serverTimestamp()

In addition, the null data type sort of enables migrations. Using the DocumentSnapshot#contains(…) method, you could check to see if a field exists and do something if it doesn’t. A better strategy would be to use a Cloud Function, but that’s beyond the scope of this article.

此外,使用null数据类型排序可以进行迁移。 使用DocumentSnapshot#contains(…)方法,您可以检查字段是否存在,如果不存在,请执行某些操作。 更好的策略是使用Cloud Function,但这超出了本文的范围。

You’ll notice documents still support arrays and maps, but how does that work if documents can only contain fields? Remember how I said you could technically nest your data? Here’s that special case: Firestore lets you store explicitly defined arrays and maps within documents, as opposed to creating a subcollection.

您会注意到文档仍然支持数组和映射,但是如果文档只能包含字段,那将如何工作? 还记得我说过您可以从技术上嵌套数据吗? 这是一种特殊情况:Firestore允许您在文档中存储显式定义的数组和映射,而不是创建子集合。

Note: there are several limits to documents in Firestore. Specifically, there is a 1MB size limit, a maximum of 20,000 properties per document, and a 500 level deep object nesting limit.

注意:Firestore中的文档有几个限制 。 具体来说, 最大限制为1MB,每个文档最多20,000个属性,以及500级深层对象嵌套限制

Properties are different from fields in that they account for all nested fields, not just the conventional root level field. In addition, as of this writing, updating a large array or map rewrites the entire array/map and will have abysmal performance on large data structures. Please use subcollections instead!

属性与字段的不同之处在于,它们考虑了所有嵌套字段,而不仅仅是常规的根级别字段。 另外,在撰写本文时,更新大型数组或映射将重写整个数组/映射,并且在大型数据结构上将具有糟糕的性能。 请改用子集合!

Because Google loves to rename things, document “keys” from the RTDB are now called ids. The last path segment of a collection or document is called an id, meaning teams/teamId1 is a document with id teamId1 under a collection with id teams. Nothing too groundbreaking, but it’s always nice to be on the same page when it comes to terminology.

由于Google喜欢重命名事物,因此RTDB中的文档“键”现在称为id 。 集合或文件的最后路径段被称为ID,这意味着teams/teamId1是ID的文件teamId1 ID为集合下teams 。 没什么太开创性的,但是在术语方面,将其放在同一页面上总是很高兴的。

Finally, since documents are one of the fundamental building blocks of Firestore, you can only get a full document. This is unlike the RTDB where you could query for as specific a field as you liked.

最后,由于文档是Firestore的基本组成部分之一,因此您只能获得完整的文档。 这与RTDB不同,您可以在其中查询所需的特定字段。

存储和检索数据 (Storing and retrieving data)

Now that you have a basic understanding of Firestore’s two fundamental building blocks — collections and documents — it’s time to look at how we can store and then get our data.

现在,您已经对Firestore的两个基本构建块(集合和文档)有了基本的了解,是时候来看看我们如何存储然后获取数据了。

The Cloud Firestore API surface is a massive improvement over the RTDB’s. So you’re unlikely to find any methods that were simply ported over (though some will look familiar).

Cloud Firestore API表面是对RTDB的巨大改进。 因此,您不太可能找到任何简单移植过来的方法(尽管有些方法看起来很熟悉)。

储存资料 (Storing data)

The first distinction you’ll notice from the RTDB is the slightly messy and spread out way of creating and updating data. Not to worry, it’ll all make sense in just a bit.

您从RTDB中注意到的第一个区别是创建和更新数据的方式有点混乱和分散。 不用担心,这一切都将在短时间内有意义。

Unlike our pets, there are no stray documents — they must all live under a collection. This means we have two places from where we can add data: a collection to add documents, and a document to add, update, or remove fields.

与我们的宠物不同,这里没有流浪文件,它们都必须生活在收藏之下。 这意味着我们有两个地方可以添加数据:一个用于添加文档的集合,一个用于添加,更新或删除字段的文档。

Let’s start by looking at the simplest way to add data, though collections:

让我们从添加集合的最简单方法入手:

We’re saying that within the teams collection, we want to add a document with all the fields from the Team POJO.

我们说,内部teams收集,我们要添加的文件从各个领域Team POJO。

Now let’s look at the more interesting case where we change document data:

现在让我们看一下更改文档数据的更有趣的情况:

The first thing to note is our scoutRef: it creates a scout inside our scouts collection, which in turn exists under a team document. As a URL, it would look something like this: teams/teamId/scouts/newScoutId.

首先要注意的是我们的scoutRef :它在我们的scoutRef集合中创建了一个scout,而反过来又存在于团队文档中。 作为URL,它看起来像这样: teams/teamId/scouts/newScoutId

The document() method returns a new DocumentReference with a random id. It’s a truly random id in the sense that it’s no longer based off of a timestamp.

document()方法返回带有随机ID的新DocumentReference 。 在不再基于时间戳的意义上,这是一个真正的随机id。

Those familiar with the RTDB will know that the push() method creates a pseudo-random key using a timestamp for native temporal sorting. Since Cloud Firestore aims to move away from being a chat-oriented database, it doesn’t make sense for them to use temporal sorting as the default mechanism.

那些熟悉RTDB的人会知道push()方法使用时间戳为本地时间排序创建了伪随机密钥。 由于Cloud Firestore的目标是远离面向聊天的数据库,因此对于他们而言,使用时间排序作为默认机制是没有意义的。

As such, this means you’ll have to manually add a timestamp field when relevant. In theory, you could use the timestamp as a document id for sorting, but that reduces flexibility.

因此,这意味着您必须在相关时手动添加timestamp字段。 从理论上讲,您可以将时间戳记用作排序的文档ID,但这会降低灵活性。

The DocumentReference contains a plethora of different ways to set and update data, from using maps and POJOs to supplying varargs. There’s a slice of pie for everyone! I’m going to focus on the POJO and specific field update methods, since those are the ones I’ve found to be most useful.

从使用映射和POJO到提供可变参数, DocumentReference包含了许多不同的设置和更新数据的方法。 每个人都有一块馅饼! 我将重点介绍POJO和特定的字段更新方法,因为这些是我发现最有用的方法。

The first method you’ll notice and probably use most often is set(Object). This one is fairly simple: it behaves exactly like the Java’s Map#set(key, value) method. If no document exists, it will create a new one. Otherwise, if a document exists, it will be overwritten.

您会注意到并且可能最常使用的第一个方法是set(Object) 。 这非常简单:它的行为完全类似于Java的Map#set(key, value)方法。 如果不存在任何文档,它将创建一个新文档。 否则,如果存在文档,它将被覆盖。

However, Google also provides SetOptions with various merge combinations to only overwrite some fields. I’ve found this to be useful when updating a user’s profile, for example. I’ll set/update their name, email, and photoUrl, but not the lastLogin field, since it isn’t part of my User POJO.

但是,Google还为SetOptions提供了各种合并组合,以仅覆盖某些字段。 例如,我发现这在更新用户的个人资料时很有用。 我将设置/更新它们的nameemailphotoUrl ,但不会设置lastLogin字段,因为它不是我的User POJO的一部分。

If you want to ensure a document exists before performing an update, the update(String, Object, Object…) method will be the right tool for the job. In this case, we are updating a specific field with a new value. If the document doesn’t exist prior to calling the update method, the update will fail. If you’d like, you can also update multiple fields at once by alternating key/value pairs in the varargs. (I personally prefer using multiple updates in a WriteBatch , which I’ll discuss later.)

如果要确保在执行更新之前存在文档,则update(String, Object, Object…)方法将是该作业的正确工具。 在这种情况下,我们将使用新值更新特定字段。 如果在调用update方法之前该文档不存在,则更新将失败。 如果愿意,还可以通过在varargs中交替键/值对来一次更新多个字段。 (我个人更喜欢在WriteBatch使用多个更新,我将在后面讨论。)

What if you wanted to update a nested field inside an object? For this use case, Google provides the FieldPath#of(String…) method. Each item inside the varargs array brings you deeper inside a nested field’s path — technically an object. For example, FieldPath.of("rootField", "child") updates the following field: myDocument/rootField/child.

如果要更新对象内的嵌套字段怎么办? 对于此用例,Google提供了FieldPath#of(String…)方法。 varargs数组中的每个项目都会带您深入嵌套字段的路径(从技术上讲是一个对象)。 例如, FieldPath.of("rootField", "child")更新以下字段: myDocument/rootField/child

Similarly, Firestore also supports the dot notation syntax which lets you reference that same field like so: rootField.child.

同样,Firestore还支持点表示法语法,该语法使您可以像这样引用同一字段: rootField.child

Cloud Firestore also includes an awesome new way to batch writes with the WriteBatch class. It’s very similar to the SharedPreferences.Editor you’ll find on Android. You can add or update documents in the WriteBatch instance, but they won’t be visible to your app until you call WriteBatch#commit(). I’ve created the standard Kotlin improvement where the batch lifecycle is managed for you — feel free to copypasta.

Cloud Firestore还提供了一种很棒的新方法,可以使用WriteBatch类批量写入。 它与Android上的SharedPreferences.Editor非常相似。 您可以在WriteBatch实例中添加或更新文档,但是在您调用WriteBatch#commit()之前,它们对您的应用程序不可见。 我创建了标准的Kotlin改进,可以为您管理批处理生命周期-随时可以复制副本。

The last important API change to note when managing data is how to delete it. Cloud Firestore has a method to delete a document—DocumentReference#delete()—but no easy way to delete an entire collection. Google provides a code sample with documentation on how to delete all documents in a collection, but they haven’t baked it into the SDK yet. This is because this method could easily fail under extreme conditions when attempting to delete thousands or even millions of documents buried in various subcollections. But Google does say they’re working on it.

管理数据时要注意的最后一个重要的API更改是如何删除它。 Cloud Firestore有一种删除文档的方法-DocumentReference DocumentReference#delete() -但没有一种简单的方法来删除整个集合。 Google提供了一个代码示例,其中包含有关如何删除集合中所有文档的文档,但是他们还没有将其烘焙到SDK中。 这是因为在尝试删除埋在各个子集合中的数千甚至几百万个文档时,这种方法在极端情况下很容易失败。 但是谷歌确实说他们正在努力。

In addition, their sample doesn’t delete subcollections either — only documents under the collection. Google doesn’t yet have a clear solution to that problem on Android either. Still, they’re providing a CLI/NodeJS API as part of firebase-tools that you can use to delete all subcollections manually or from a Cloud Function.

此外,他们的样本也不会删除子集合-仅删除集合下的文档。 Google在Android上也没有针对该问题的明确解决方案。 尽管如此,他们还是提供了CLI / NodeJS API作为firebase-tools一部分,可用于手动删除所有子集合或从Cloud Function中删除所有子集合。

In my case, I don’t let users create random collection names so I can delete all my subcollections by getting their parent document ids.

就我而言,我不允许用户创建随机集合名称,因此我可以通过获取其父文档ID来删除所有子集合。

I’ve rewritten their sample with more functionality and a cleaner API in Kotlin:

我在Kotlin中使用更多功能和更清洁的API重写了他们的示例:

Whew, we’ve covered most everything you’ll need to know about storing data!

简而言之,我们涵盖了您需要存储的大多数信息!

检索数据 (Retrieving data)

The first thing to note is that I’m using the word retrieving instead of reading. This is because Firestore provides two very clear-cut ways of retrieving data: either through a single read (aka a get), or through a series of reads (aka a snapshot listener).

首先要注意的是,我正在使用单词检索而不是阅读。 这是因为Firestore提供了两种非常清晰的数据检索方式:通过一次读取(又称get ),或通过一系列读取(又称快照侦听器 )。

获取数据 (Getting data)

Let’s start by exploring ways to read data once. In the RTDB, you had the addListenerForSingleValueEvent() method, but it was full of bugs and edge cases. I think Frank van Puffelen — a Googler — summed it up best:

让我们从探索一次读取数据的方法开始。 在RTDB中,您具有addListenerForSingleValueEvent()方法,但其中充满了错误和极端情况。 我认为Google员工Frank van Puffelen总结得最好:

The best way to solve this is to not use a single-value listener.
解决此问题的最佳方法是不使用单值侦听器。

Yeah. There’s definitely a problem when you tell your own users not to use a product you’re selling.

是的 当您告诉自己的用户不要使用您要销售的产品时,肯定存在问题。

Cloud Firestore completely revamps the entire data retrieval experience with better and more intuitive APIs.

Cloud Firestore使用更好,更直观的API彻底改善了整个数据检索体验。

First, a note on offline capabilities. The RTDB wasn’t designed as an offline first database — offline capabilities were more of an afterthought since the db was ported over from pre-Google Firebase. On the other hand, Cloud Firestore isn’t exactly an offline first database since it’s also designed to be realtime. But I would consider its offline capabilities to be first class citizens along with the realtime stuff.

首先,关于脱机功能的说明。 RTDB并非设计为脱机优先数据库—脱机功能更多是事后才想到的,因为该数据库是从Google之前的Firebase移植过来的。 另一方面,Cloud Firestore并非完全是脱机优先数据库,因为它也被设计为实时的。 但是我认为它的脱机功能以及实时功能是一流的公民。

Given those improvements, offline support is enabled by default (except for web), and data is stored in a SQLite database using Android’s native APIs. I don’t know about you, but I find it more than a little ironic that a NoSQL database needs a SQL database to work.

有了这些改进,默认情况下将启用脱机支持(Web除外),并且使用Android的本机API将数据存储在SQLite数据库中。 我不了解您,但我发现NoSQL数据库需要SQL数据库才能工作,这有点讽刺。

For the curious, Firestore’s SQL database is named firestore.$firebaseAppName.$projectId.(default). In addition, they lock it using PRAGMA locking_mode = EXCLUSIVE to improve performance and prevent multi-process access. If you’re really curious, here are the tables and queries I’ve found so far:

出于好奇,FirestoreSQL数据库名为firestore.$firebaseAppName.$projectId.(default) 。 此外,他们使用PRAGMA locking_mode = EXCLUSIVE锁定它,以提高性能并防止多进程访问。 如果您真的很好奇,那么这里是到目前为止我找到的表和查询:

I did some more digging and found a few other things. For example, GRCP devs really like enums. You know what they say, “If something is bad for you, do more of it!”

我做了一些进一步的挖掘,发现了其他一些东西。 例如,GRCP开发人员非常喜欢枚举 。 您知道他们在说什么:“如果某件事对您不利,请多做点事!”

With that aside, let’s explore our first method: DocumentReference#get(). This is the simplest and most basic way to retrieve data: it replaces the RTBD's addListenerForSingleValueEvent() method with several notable improvements.

除此之外,让我们探索第一个方法: DocumentReference#get() 。 这是检索数据的最简单,最基本的方法:它以一些显着的改进代替了RTBD的addListenerForSingleValueEvent()方法。

First, it returns a Task<DocumentSnapshot> . This makes far more sense than using the same event model API as you would for snapshot listeners from the RTDB. Now, you can use all of Play Services’s lovely Task APIs to add your success and failure listeners. You can even attach them to an activity lifecycle if needed.

首先,它返回Task<DocumentSnapsh ot>。 这与使用用于RTDB快照侦听器的事件模型API相比要有意义得多。 现在,您可以使用所有播放服务的升ovel Ÿ任务的API添加您的成功和失败的听众。 如果需要,您甚至可以将它们附加到活动生命周期。

Second, offline support actually makes sense when using get(). If the device is online, you’ll get the most up-to-date copy of your data directly from the server. If the device is offline and has cached data, you’ll immediately get that cache. And finally, if there’s no cached data, you’ll immediately get a failure event with error code FirebaseFirestoreException.Code#UNAVAILABLE. TLDR: you’ll get the most up-to-date data that can be retrieved in the device’s current network state.

其次,使用get()时,脱机支持实际上是有意义的。 如果设备在线,则可以直接从服务器获取最新数据副本。 如果设备离线且已缓存数据,您将立即获得该缓存。 最后,如果没有缓存的数据,您将立即收到带有错误代码FirebaseFirestoreException.Code#UNAVAILABLE的失败事件。 TLDR:您将获得可以在设备当前网络状态下检索的最新数据。

I’ll dive into queries in just a bit, but for now, I’ll just mention that the Query#get() method returning a Task<QuerySnapshot> is also available with the same behavior as described above.

我将稍微介绍一下查询,但是现在,我只提到返回Task<QuerySnapsh ot>的Query#get()方法也具有上述相同的行为。

In other notable news, the Query#getRef() method was removed to support a possible future where a query doesn’t depend on a CollectionReference. Just like in the RTDB, CollectionReference extends Query to support easily starting a query. But in the RTDB, you used to be able to jump back and forth between queries and refs. This is no longer the case in Firestore. I’ve found this to be a mild inconvenience, but nothing too major.

在其他值得注意的新闻中,删除了Query#getRef()方法以支持查询不依赖CollectionReference将来。 就像在RTDB中一样, CollectionReference扩展了Query以支持轻松启动查询。 但是在RTDB中,您曾经能够在查询和引用之间来回跳转。 在Firestore中,情况不再如此。 我发现这带来了轻微的不便,但没有什么太大的问题。

监听数据 (Listening for data)

Of course, this is Firebase — so we also want our sweet, sweet realtime capabilities. The API surface for queries was also completely revamped to be cleaner and clearer.

当然,这是Firebase-所以我们也想要我们甜美的实时功能。 用于查询的API界面也进行了彻底修改,使其更加整洁。

Let’s start by looking at how you would get all the documents in a collection.

让我们从如何获取集合中的所有文档开始。

Do you remember the difference between addValueEventListener() and addChildEventListener() from the RTDB? And have you ever wished you could get a bit of both worlds? Me too. Thankfully, this is exactly what Google has done with Cloud Firestore: you’ll get the entire list of documents and a list of changes and possible exceptions all in one monolithic callback.

您还记得RTDB中的addValueEventListener()addChildEventListener()之间的区别吗? 您是否曾经希望可以同时获得这两个世界? 我也是。 值得庆幸的是,这正是谷歌已经与云公司的FireStore做:你会得到的文件的整个列表的变化可能的例外都在一个整体回调的列表。

I’m not sure I like the combined data/exception model, but it makes sense in a Java 8 world with functional interfaces. For example, here’s a nice lambdazed callback:

我不确定我是否喜欢组合的数据/异常模型,但是在具有功能接口的Java 8世界中,它是否有意义。 例如,这是一个很好的lambdazed回调:

Let’s start with the error case, since that’s what all good developers should think about first, right? ?

让我们从错误的情况开始,因为这是所有好的开发人员都应该首先考虑的,对吗? ?

FirebaseFirestoreException is relatively simple compared to the RTDB. First, it’s actually an exception! Whaaat? An error that actually extends Exception—who would have thought!? This makes crash reporting dead simple: just report the exception which includes error codes and everything. It’ll look nice and pretty like this:

与RTDB相比, FirebaseFirestoreException相对简单。 首先,这实际上是一个例外! 哇 实际上扩展了Exception的错误-谁会想到的! 这使崩溃报告完全无效:仅报告包含错误代码和所有内容的异常。 这样看起来会很漂亮:

Exception com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.

异常com.google.firebase.firestore.FirebaseFirestoreException:PERMISSION_DENIED:缺少权限或权限不足。

With that aside, let’s move on to the exciting stuff: QuerySnapshot. It contains document changes, the full list of documents, and some other data I’ll explore in just a bit.

除此之外,让我们继续进行令人兴奋的事情: QuerySnapshot 。 它包含文件的变化 ,的完整列表文件 ,我会在短短的一点探讨一些其他数据。

I’ve provided links to all the relevant classes, because I’m going to skip over those in favor of using FirebaseUI. I’ll explore this in depth later when we’re putting everything together.

我提供了所有相关类的链接,因为我将跳过那些支持使用FirebaseUI的类。 当我们将所有内容放在一起时,我将对此进行深入探讨。

As a quick summary, you can differentiate between different update types, iterate over the QuerySnapshot to get each DocumentSnapshot in pretty Java 5 for loops, convert the entire list to a bunch of POJOs (not recommended for performance reasons, will discuss later), and convert individual documents to a POJO or access specific field information. So basically everything you’d expect from a nice API.

作为一个简短的摘要,您可以区分不同的更新类型,遍历QuerySnapshot以获得漂亮的Java 5 for循环中的每个DocumentSnapshot ,将整个列表转换为一堆POJO(出于性能原因不推荐使用,稍后将讨论),以及将单个文档转换为POJO或访问特定的字段信息。 因此,基本上,您期望从一个不错的API获得的所有信息。

However, I do want to explore listener registration and QueryListenOptions—a new way get information about your offline status.

但是,我确实想探索侦听器注册和QueryListenOptions -一种获取有关您的离线状态信息的新方法。

Those two concepts will be easier to understand with a code sample, so here goes nothing:

通过代码示例,这两个概念将更容易理解,因此这里一无所获:

The basic idea of this method is to wait until data is received directly from the server.

此方法的基本思想是等待,直到直接从服务器接收到数据。

The first thing to note is listener registration — it’s kinda painful. I’ve spent some time thinking about it, and I’ve come to the conclusion that Google made the right choice. It should be painful to nudge you in the right direction.

首先要注意的是监听者注册-这很痛苦。 我花了一些时间考虑一下,得出的结论是Google做出了正确的选择。 向正确的方向轻推您应该很痛苦。

Ok, let’s back up a bit. In the RTDB, you’re used to removing the listener callback instance directly from the Query class. This was a nice API, but it let you do terrible things like accidentally leak your Contexts. The new API returns a ListenerRegistration whose only method is remove()—pretty self-explanatory.

好吧,让我们备份一下。 在RTDB中,您习惯于直接从Query类中删除侦听器回调实例。 这是一个不错的API,但是它使您可以做一些糟糕的事情,例如意外泄漏Context 。 新的API返回一个ListenerRegistration其唯一方法是remove()相当不言自明。

This new listener registration method forces you to rethink your approach to retrieving data. Here’s a simple guide to choosing which API to use:

这种新的侦听器注册方法迫使您重新考虑检索数据的方法。 这是选择使用哪个API的简单指南:

  1. If your data isn’t being displayed to the user, you should probably be using one of the get() methods that use the same listener registration mechanism internally as shown above. (Google’s suffering for you ?)

    如果未将数据显示给用户,则可能应该使用get()方法之一,该方法在内部使用相同的侦听器注册机制,如上所示。 (Google对您的痛苦吗?)

  2. If your data is tied to UI, you should use the addSnapshotListener(Activity, ...) variant which automatically manages lifecycle for you by unregistering in Activity#onStop().

    如果数据绑定到UI,则应使用addSnapshotListener(Activity, ...)变体,该变体通过在Activity#onStop()注销来自动为您管理生命周期。

  3. If your data is tied to a list like a RecyclerView, hold your horses — I’m going to detail the much-improved FirebaseUI library later on, which will automatically handle nearly everything for you.

    如果您的数据与RecyclerView类的列表相关联,请放心使用-稍后,我将详细介绍经过改进的FirebaseUI库,它将自动为您处理几乎所有内容。

  4. If you don’t fall into the above categories, then you should consider using FirebaseUI (again!) which I’ll detail later (again!). Otherwise, just avert your eyes. ?

    如果您不属于上述类别,则应该考虑使用FirebaseUI(再次!),我将在稍后详细介绍(再次!)。 否则,请睁开眼睛。 ?

Ok, so the listener registration API is painful, but intentionally so to nudge you into picking the right tool for the job.

好的,所以侦听器注册API很痛苦,但是故意让您轻而易举地选择适合该工作的工具。

Now let’s take a look at the QueryListenOptions. Remember how I said Cloud Firestore considers offline support a first class citizen? Here’s where they address the final pain points devs experienced with the RTDB. They still don’t offer a way to customize how your data is cached, but personally, I don’t see any value in that kind of customization: the API should be smart enough to manage that stuff for me—and it is with Firestore.

现在,让我们看一下QueryListenOptions 。 还记得我说过Cloud Firestore认为离线支持是一流的公民吗? 在这里,他们解决了RTDB开发人员遇到的最终难题。 他们仍然没有提供自定义缓存数据方式的方法,但是就我个人而言,这种自定义方法没有任何价值:API应该足够聪明,可以为我管理这些东西,而Firestore就是这样。

The first method you’ll find in your listen options is called includeQueryMetadataChanges() and the second is called includeDocumentMetadataChanges(). Both of these are tied to SnapshotMetadata`s isFromCache() and hasPendingWrites() respectively.

在侦听选项中找到的第一个方法称为includeQueryMetadataChanges() ,第二个称为includeDocumentMetadataChanges() 。 这两个都分别绑定到SnapshotMetadataisFromCache()hasPendingWrites()上。

For a given QuerySnapshot, isFromCache() will have the same value for each DocumentSnapshot’s metadata and for query’s metadata itself. This means you can find out if your data is up-to-date with the server either from a QuerySnapshot or from a DocumentSnapshot—it doesn’t matter. Either the entire query is considered to be up-to-date, or not — there’s no in-between state like the API would have you believe. In theory, one of your documents could actually be up-to-date if another active listener includes that document in its results, but Google has opted for simplicity and doesn’t surface this information in the API.

对于给定的QuerySnapshotisFromCache()对于每个DocumentSnapshot的元数据和查询的元数据本身将具有相同的值。 这意味着您可以通过QuerySnapshotDocumentSnapshot来确定服务器上的数据是最新的—没关系。 整个查询要么被认为是最新的,要么没有,就像您希望的那样,没有中间状态。 从理论上讲,如果另一个活动的侦听器在其结果中包含该文档,则您的其中一个文档实际上可能是最新的,但是Google选择了简单性,并且不会在API中显示此信息。

On the other hand, hasPendingWrites() can have a different value for each DocumentSnapshot. This is what you’d expect, and there aren’t any special edge cases or tricks.

另一方面, hasPendingWrites()对于每个DocumentSnapshot可以具有不同的值。 这就是您所期望的,并且没有任何特殊的边缘情况或技巧。

To summarize:

总结一下:

  • Use includeQueryMetadataChanges() if you’d like know whether a query and all its documents are up-to-date with the server.

    如果您想知道查询及其所有文档是否与服务器最新,请使用includeQueryMetadataChanges()

  • Use includeDocumentMetadataChanges() if you’d like to know about per-document changes in pending write status.

    如果您想了解未决写入状态中每个文档的更改,请使用includeDocumentMetadataChanges()

One last tidbit before I move on: all the addSnapshotListener methods are also duplicated in DocumentReference so you can get updates about just a single document if needed.

在我继续之前的最后一个小addSnapshotListenerDocumentReference中也复制了所有addSnapshotListener方法,因此如果需要,您可以仅获取单个文档的更新。

查询数据 (Querying data)

Ahhh… More than 3,000 words later, we finally get into the meat of Cloud Firestore.

啊……超过3000个单词,我们终于了解了Cloud Firestore。

I don’t have any statistics to back this statement, but I think that by far the biggest complaint about the RTDB is the lack of proper querying abilities. Here’s another quote from Pier Bover’s article:

我没有任何统计数据来支持此声明,但是我认为到目前为止,有关RTDB的最大抱怨是缺乏适当的查询功能。 这是Pier Bover文章的另一句话:

Really? Google is providing a data service with no searching or filtering capabilities? Yeah. Really.
真? Google提供的数据服务没有搜索或过滤功能? 是的 真。

Since Cloud Firestore is backed by GCP’s Cloud Datastore, queries are first class citizens.

由于Cloud Firestore由GCP的Cloud Datastore支持,因此查询是一等公民。

Let’s go back to our new and improved data structure. But to save you from aggressively scrolling up for a minute, here it is reposted:

让我们回到新的和改进的数据结构。 但是为了避免让您主动滚动一分钟,此处将其重新发布:

Since we have an infinite list of teams, how do we get a specific user’s teams? In the RTDB, we would have stored the data following a pattern akin to this: teams/uid1/teamKey1. With Cloud Firestore, we flipflop the user’s id and the team id so that the pattern looks more like this: teams/teamKey1/owners/uid1.

由于我们有无数的团队,因此如何获得特定用户的团队? 在RTDB中,我们将按照类似于以下的模式存储数据: teams/uid1/teamKey1 。 使用Cloud Firestore,我们可以翻转用户ID和团队ID,以便模式看起来更像这样: teams/teamKey1/owners/uid1

Now we can query for the user’s teams like so:

现在,我们可以像这样查询用户的团队:

We’re telling Firestore to look at the owners field in all documents under the teams collection for a document with id uid equal to true.

我们告诉公司的FireStore看owners场中的所有文件下teams集合为一个文件ID为uid等于true

Unfortunately, this method doesn’t support ordering. So instead, we’ll write the following query:

不幸的是,此方法不支持排序。 因此,我们将编写以下查询:

This query has the advantage of supporting ordering, but it comes with similar issues to the RTDB: updating those sorting values is going to be a pain.

该查询具有支持排序的优点,但它具有与RTDB类似的问题:更新这些排序值将是一件很麻烦的事情。

In my case, the sorting values are always static: they’re either the team number or the document creation timestamp. Because I’m never going to update those sorting values, this query works perfectly for me.

就我而言,排序值始终是静态的:它们是团队编号或文档创建时间戳。 因为我永远不会更新这些排序值,所以此查询非常适合我。

On the other hand, you might have different constraints — remember, I need my data to be structured in a way that supports easily sharing teams and templates across users. If this isn’t your case, you should take a look at Google’s suggested structures and their solutions to common problems.

另一方面,您可能会有不同的限制-请记住,我需要以一种支持在用户之间轻松共享团队和模板的方式来结构化我的数据。 如果您的情况并非如此,则应查看Google 建议的结构及其对常见问题解决方案

Since the queries you write will depend on your app’s specific constraints, I’m not going to delve into them too much. But I will point out that Cloud Firestore supports compound queries.

由于您编写的查询将取决于应用程序的特定约束,因此我不会过多地研究它们。 但我会指出,Cloud Firestore支持复合查询

One last notable change from the RTDB before I move on: priorities aren’t a thing anymore. Since Firestore properly supports ordering and querying, they opted to remove the .priority field you could find in the RTDB from Firestore documents.

在继续之前,RTDB的最后一个显着变化是:优先级已不再是问题。 由于Firestore正确支持排序和查询,因此他们选择从Firestore文档中删除RTDB中可以找到的.priority字段。

However, if you still want to order your documents by id for some reason, Firestore provides the FieldPath#documentId() method for exactly that purpose.

但是,如果出于某种原因FieldPath#documentId() ID排序文档,则Firestore正是出于此目的提供了FieldPath#documentId()方法。

安全规则 (Security rules)

Security rules in Firestore have gotten a bit worse for wear, in my opinion. However, for those familiar with Firebase Storage, you’ll feel right at home. Google has merged its database rules technology with the rest of GCP.

我认为,Firestore中的安全规则在磨损方面已变得更糟。 但是,对于那些熟悉Firebase Storage的人来说,您会感到宾至如归。 Google已将其数据库规则技术与GCP的其余部分合并。

On the other hand, for those coming from a JSON world with the RTDB, Firestore’s new rules syntax is a bit convoluted. If you deploy rules in your CI build, you’ll have to either edit them in the Firebase Console and then copy the rules to your local editor, or edit them in a txt file. Gross.

另一方面,对于那些来自带有RTDB的JSON世界的用户,Firestore的新规则语法有些复杂。 如果您在CI版本中部署规则,则必须在Firebase控制台中对其进行编辑,然后将规则复制到本地编辑器中,或者在txt文件中进行编辑。 毛。

Here’s what the simplest possible set of rules looks like:

这是最简单的规则集:

Google actually has surprisingly good documentation on security rules — I’ve personally been able to solve nearly all of my problems just by reading the docs. I will still go over a few gotchas from the RTDB developer’s point of view (assuming you’ve at least skimmed the docs).

Google实际上有关于安全规则的出色文档 -我个人仅阅读文档就能够解决几乎所有问题。 我仍然会从RTDB开发人员的角度来探讨一些陷阱(假设您至少已略过文档)。

First, the read keyword is an array of get and list, and the write keyword is an array of create, update, and delete . Each keyword is self-explanatory except for list — it applies to queries, meaning not a single “get.” Each of these keywords can be used individually, but the read and write ones were provided for convenience.

首先, read关键字是getlist的数组,而write关键字是createupdatedelete的数组。 除list外,每个关键字都是不言自明的-它适用于查询,意味着不是一个“ get”。 这些关键字可以单独使用,但readwrite分别提供了方便的。

On a related note, you’ll usually end up splitting up your write keywords to allow deletion. For example, using the request object to check write validity fails if a user is trying to delete the data in question. In addition, if you’re checking to see if someone is an owner, you’ve introduced a security flaw. Anyone can add themselves, since the new data is being checked instead of the old.

与此相关的是,您通常最终会拆分write关键字以允许删除。 例如,如果用户试图删除所讨论的数据,则使用request对象检查写入有效性将失败。 此外,如果要检查某人是否为所有者,则说明存在安全漏洞。 任何人都可以添加自己,因为正在检查新数据而不是旧数据。

Here’s some sample rules to put those words into code:

以下是一些将这些词放入代码中的示例规则:

There’s another major difference from the RTDB developer’s perspective: rule evaluation is shallow by default. This goes along nicely with the (sub)collection model, but requires a small shift in mindset.

从RTDB开发人员的角度来看,还有另一个主要区别:默认情况下,规则评估很浅。 这与(sub)收集模型很好地配合,但是需要心态稍有改变。

For example, the request variable does not contain information about its parent document. At first, I wanted to check from a document inside a subcollection if a parent document had some field. But of course this doesn’t work, because the subcollection is just a link inside of the parent document.

例如,该request变量包含有关它的父文档的信息。 首先,我想从子集合内的文档中检查父文档是否具有某些字段。 但是,这当然是行不通的,因为子集合只是父文档内部的链接。

Because rules are shallow, you have to be careful when using the double star operator (variable=**) since its resources won’t contain parent document information. In addition, there’s some funkiness with the variable:

由于规则很浅,因此使用双星运算符( variable=** )时必须小心,因为其资源将不包含父文档信息。 此外,该变量还具有一些趣味性:

FirebaseUI (FirebaseUI)

Now that you have a complete understanding of Cloud Firestore’s capabilities along with its differences and improvements from the RTDB, let’s take a look at how we can put all of this together to build some UI.

既然您已经完全了解Cloud Firestore的功能以及它与RTDB的区别和改进,那么让我们看一下如何将所有这些结合在一起以构建一些UI。

FirebaseUI consists of several components including auth and storage, but we’ll focus on the firestore module.

FirebaseUI由几个组件组成,包括authstorage ,但我们将重点介绍firestore模块。

In the querying section, I mentioned several times that FirebaseUI could help us. We’ll start by looking at how we can improve upon QuerySnapshot’s toObjects() method.

在查询部分,我多次提到FirebaseUI可以帮助我们。 我们将从研究如何改进QuerySnapshottoObjects()方法开始。

There are two main problems with using the toObjects() method:

使用toObjects()方法存在两个主要问题:

  1. Performance is going to suck, especially with large lists. On every update your EventListener receives, Firestore will recreate every object — changed or not — all at once using reflection. Ouch.

    性能将下降,特别是对于大型列表。 在EventListener收到的每次更新中,Firestore都会使用反射一次重新创建每个对象(无论是否更改)。 哎哟。

  2. There’s no customization available. For example, I like my model objects to have a ref field so that I can easily update them later. However, I don’t want to actually store the ref in the database because that would just be pointless duplication.

    没有自定义设置。 例如,我希望模型对象具有ref字段,以便以后可以轻松更新它们。 但是,我不想将引用实际存储在数据库中,因为那将是毫无意义的重复。

While you might be thinking, “well, I’ll just create a list and update it whenever new objects come in,” FirebaseUI does exactly that for you so you don’t have to write boilerplate code.

您可能会想,“好吧,我将创建一个列表并在出现新对象时对其进行更新”,FirebaseUI正是为您完成的,因此您无需编写样板代码。

FirestoreArray—as it’s aptly named — is an array of snapshots from Firestore converted to your POJO model objects. Its constructor takes in a Firestore Query, a SnapshotParser<;T>, and optionally, query options. It starts listening for data whenever you add one or more ChangeEventListeners and will automatically stop listening when the last listener is removed.

FirestoreArray (恰当命名)是从Firestore转换为POJO模型对象的快照数组。 它的构造函数接受Firestore QuerySnapshotParser< ; T>以及可选的查询选项。 每当您添加一个或more ChangeEventLi引导器时,它将开始侦听数据,并且在删除最后一个侦听器时将自动停止侦听。

The ChangeEventListener will notify you when each object changes, when an entire update has been processed, and when any errors occur. The SnapshotParser<;T> has a single method — parseSnapshot—which is responsible for converting each DocumentSnapshot into your model POJO of type T.

当每个对象更改,何时处理了整个更新以及何时发生任何错误时, ChangeEventListener都会通知您。 The SnapshotParser< ;T> has a single met hod — parseSn apshot—which is responsible for converting each DocumentSn apshot into your model POJO of type T.

Since FirestoreArray implements List<;T> , this setup lets you easily listen for updates to your model objects with minimal hassle.

Since FirestoreArray implements List< ;T> , this setup lets you easily listen for updates to your model objects with minimal hassle.

In terms of performance, FirestoreArray uses Android’s native LruCache to lazily parse objects as needed. For now, we’ve set the max cache size to 100, but if you feel you’ll need a bigger (or smaller) cache size, we’d love to know your use cases in a GitHub issue.

In terms of performance, FirestoreArray uses Android's native LruCache to lazily parse objects as needed. For now, we've set the max cache size to 100 , but if you feel you'll need a bigger (or smaller) cache size, we'd love to know your use cases in a GitHub issue .

Since this is FirebaseUI, we let you easily map your FirestoreArray to a RecyclerView with the FirestoreRecyclerAdapter and its FirestoreRecyclerOptions.

Since this is Firebase UI , we let you easily map your FirestoreArray to a RecyclerView with the FirestoreRecyclerAdapter and its FirestoreRecyclerOptions .

There are a few interesting recycler options, notably the ability to pass in an Android Architecture Components LifecyleOwner with which we’ll automatically manage the FirestoreArray’s lifecycle for you.

There are a few interesting recycler options, notably the ability to pass in an Android Architecture Components LifecyleOwner with which we'll automatically manage the FirestoreArray 's lifecycle for you.

Ok, that was a lot of words. Here’s what it would look like all put together with Architecture Components while taking auth states into consideration:

Ok, that was a lot of words. Here's what it would look like all put together with Architecture Components while taking auth states into consideration:

Other tidbits (Other tidbits)

For web developers, Firestore comes complete with full offline support, unlike the RTDB which had… nothing? Yep. Cheers to offline support as a first class citizen for all mobile platforms!

For web developers, Firestore comes complete with full offline support, unlike the RTDB which had… nothing? 是的 Cheers to offline support as a first class citizen for all mobile platforms!

Also, if you’d like extra info about migrating from the RTDB to Cloud Firestore, like how to keep your data synchronized during the transition period, you’ll find documentation here.

Also, if you'd like extra info about migrating from the RTDB to Cloud Firestore, like how to keep your data synchronized during the transition period, you'll find documentation here .

结语 (Wrap up)

I hope you’ve enjoyed this deep dive into Firebase’s new database and are ready to start migrating your apps. Feel free to ask me any questions or clarifications! And if you found this article helpful, don’t hesitate to give me some claps ?.

I hope you've enjoyed this deep dive into Firebase's new database and are ready to start migrating your apps. Feel free to ask me any questions or clarifications! And if you found this article helpful, don't hesitate to give me some claps ?.

If you’ve been enjoying quotes bashing the RTDB, here’s one last quote for your viewing pleasure:

If you've been enjoying quotes bashing the RTDB, here's one last quote for your viewing pleasure:

People have made [the RTDB] work for prod apps, but they are forcing a square peg into a round hole. - Eric Kryski
People have made [the RTDB] work for prod apps, but they are forcing a square peg into a round hole. - Eric Kryski

Ouch, that burns! While the RTDB may have been an uncontrollable wildfire, Cloud Firestore is a fiercely powerful flame you can wield with purpose to build better apps!

Ouch, that burns! While the RTDB may have been an uncontrollable wildfire, Cloud Firestore is a fiercely powerful flame you can wield with purpose to build better apps !

Cloud Firestore | FirebaseUse our flexible, scalable NoSQL cloud database to store and sync data for client- and server-side development.firebase.google.com

Cloud Firestore | Firebase Use our flexible, scalable NoSQL cloud database to store and sync data for client- and server-side development. firebase.google.com

翻译自: https://www.freecodecamp.org/news/rtdb-to-firestore-fd8da8149877/

firebase创建数据库

 类似资料: