flutter 包错误_Flutter + Firestore:您可能使用错误。

flutter 包错误

Cloud Firestore can be very powerful when coupled with Flutter, but also tricky for Production-Ready Apps where a good architecture is paramount.

当与Flutter结合使用时,Cloud Firestore可能会非常强大,但对于拥有良好架构至关重要的生产就绪型应用程序来说,它也很棘手。

Firebase Cloud Firestore’s Flutter integration is nothing short of amazing. It takes advantage of Dart’s language features, enabling fast development cycles with almost zero backend code. But as Uncle Ben once said: “With great power comes great responsibility” or in our case: All that flexibility can come at a price.

Firebase Cloud Firestore的 Flutter集成令人惊叹。 它利用Dart的语言功能,以几乎为零的后端代码实现快速开发周期。 但是,正如本伯叔叔曾经说过的那样: “强大的力量伴随着巨大的责任”,或者在我们的案例中:所有的灵活性都可以付出代价。

Production-Ready Apps need to be robust, so regular code maintenance or feature updates won’t easily bring along unnoticed bugs. The key to achieve it is following best practices. Adopting tried-and-true architectures and sound design patterns, will provide a much more pleasant experience to your users.

适用于生产环境的应用程序必须具有强大的功能,因此常规的代码维护或功能更新不会轻易带来未发现的错误。 实现这一目标的关键是遵循最佳实践。 采用可靠的架构和声音设计模式,将为您的用户提供更加愉悦的体验。

This article explores why most examples provided on Firestore’s “Tutorials” and “Quick Starts” don’t follow these best practices, so you shouldn’t strictly copy them, but instead extract the essence of what they are trying to teach.

本文探讨了为什么Firestore的“教程”和“快速入门”中提供的大多数示例都没有遵循这些最佳实践,因此您不应严格照搬它们,而应摘录它们试图教的内容。

Note: This article is meant for developers already familiarized with Flutter and Firebase Cloud Firestore. If you aren’t there yet, I suggest you hone your skills a bit more and then come back here later.

注意:本文适用于已经熟悉 Flutter Firebase Cloud Firestore的 开发人员 如果您还没有,我建议您 磨练 你的 技能 多一点,然后稍后再回到这里。

问题 (The Problem)

You have probably seen a piece of code like this before:

您之前可能已经看过一段代码:

class BookList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: Firestore.instance.collection('books').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        switch (snapshot.connectionState) {
          case ConnectionState.waiting: return new Text('Loading...');
          default:
            return new ListView(
              children: snapshot.data.documents.map((DocumentSnapshot document) {
                return new ListTile(
                  title: new Text(document['title']),
                  subtitle: new Text(document['author']),
                );
              }).toList(),
            );
        }
      },
    );
  }
}

This code snippet comes straight out of Firestore’s own examples, as we can see the database query code is heavily intermingled with the UI code. While answering questions on StackOverflow I stumble upon this pattern all the time, but the funny thing is: I can’t even blame Firestore for this, as they have a pretty good reason for including it on their samples, as we will see below.

该代码段直接来自Firestore自己的示例 ,因为我们可以看到数据库查询代码与UI代码大量混杂在一起。 在回答关于StackOverflow的问题时,我总是无意中发现这种模式,但有趣的是:我什至不能怪Firestore,因为他们有充分的理由将其包括在样本中,如下所示。

So, what’s the problem with that code? Why does Firestore use it on their samples? Can we make it better?

那么,该代码有什么问题? 为什么Firestore在其样本上使用它? 我们可以做得更好吗?

Writing high-quality software isn’t just about aiming for code that “works,” there’s more to be baked into your code to bring it to the next level.

编写高质量的软件并不仅仅是针对“工作”的代码,还有更多内容可以嵌入到您的代码中,从而将其带入一个新的高度。

Concepts like testability and maintainability are fundamental for projects expected to hit the market and deliver a continuous experience of robustness throughout future App updates. Of course there is much more to Software Quality than those two topics, but we will focus on those as they are essential to understand the root of our problem.

可测试性可维护性等概念对于预计将要投放市场的项目至关重要,并在未来的应用程序更新中提供持续的健壮性体验。 当然, 软件质量比这两个主题要重要得多,但是我们将重点关注这些主题,因为它们对于理解问题的根源至关重要。

Keep in mind we won’t go deep into those two topics, otherwise we risk deviating too much from the original intent of this article, but a brief comprehension is essential to understand the problem we are accessing and why it is highly connected to software quality.

请记住,我们不会深入探讨这两个主题,否则我们可能会冒险偏离本文的初衷,但是对于理解我们正在访问的问题以及为什么它与软件质量高度相关的原因,有一个简短的理解是必不可少的。

可测性 (Testability)

You want your code to be testable, this means being able to build automated tests (Unit Tests, Integration Tests, etc.) that can be triggered whenever you want to, this way you can be sure that new modifications won’t break functionality already present into your App.

您希望代码是可测试的,这意味着能够构建可以在任何时候触发的自动化测试(单元测试,集成测试等),这样您可以确保新的修改不会破坏功能呈现到您的应用中。

You can also TDD and be sure that every new code you write will function as intended in every possible scenario, as described and covered by your previously written tests.

您也可以TDD,并确保您编写的每个新代码在每种可能的情况下都能按预期运行,如先前编写的测试所描述和涵盖的那样。

But writing testable software isn’t easy. How do you test code that needs to receive data from a Database? Or a REST API call from the internet? What if the network is down?

但是编写可测试的软件并不容易。 您如何测试需要从数据库接收数据的代码? 还是来自互联网的REST API调用? 如果网络中断怎么办?

If you get a message saying that your tests failed, are you sure they failed because your code has a bug, or is it just the Internet connection that’s down?

如果您收到一条消息,说明您的测试失败,是否确定由于代码有错误而导致测试失败,或者仅仅是Internet连接断开了?

Let’s say your tests execute flawlessly, how about running 1000 tests, each of them needing a round-trip to the server? How long will it take for your tests to complete?

假设您的测试可以完美执行,那么运行1000个测试又如何,每个测试都需要往返服务器? 您的测试需要多长时间才能完成?

What if your tests need to create or delete data? Can you risk your precious Database with code that hasn’t been tested yet?

如果您的测试需要创建或删除数据怎么办? 您是否可以使用未经测试的代码来冒险珍贵的数据库?

可维护性 (Maintainability)

This is a very broad concept but in a nutshell it’s the idea of making your code ready for future changes, as Heraclitus once said “The only constant in life is change,” and so it is in software as well.

这是一个非常广泛的概念,但总的来说,它是使代码为将来的更改做好准备的想法,就像Heraclitus曾经说过“生命中唯一不变的就是变化” ,在软件中也是如此。

Additional features will be requested, bugs would have to be fixed, you know the drill. Can your teammates easily understand what your code does? Can your future self, 3 years from now, read your code and understand it? How easy it is to adapt it to something else? These questions are closely related to code readability and coupling.

您将了解其他功能,需要附加功能,错误必须修复。 您的队友可以轻松理解您的代码做什么吗? 从现在起3年后,您未来的自我能否阅读并理解您的代码? 使其适应其他事物有多容易? 这些问题与代码的可读性密切相关 耦合

Many times maintainability and testability walk hand in hand. For example, if your project is heavily coupled with a bunch of code calling each other, all of that functionality mixed up together, not only it will be hard to maintain, it’s really hard to test as well. If a test fails, how do you know which of the countless inner parts of that heavily coupled code that’s actually broken? Can you easily identify and point out the culprit?

许多时候,可维护性和可测试性齐头并进。 例如,如果您的项目中包含大量相互调用的代码,那么所有功能混在一起,不仅难以维护,而且也很难进行测试。 如果测试失败,您如何知道该重耦合代码中数不清的内部部分实际上被破坏了? 您可以轻松识别并指出罪魁祸首吗?

So how does testability and maintainability relate to our Firestore previous sample?

那么可测试性和可维护性与我们的Firestore先前样本有何关系?

Well, can you test that code? No, you can’t as it will send a network request to your Firestore instance. What if one of your tests says a ListTile has wrong data? Is it because there’s wrong data on the database or is it because your build function doesn’t handle the data correctly, thus constructing the ListTile incorrectly? You can’t know because you can’t separate both parts to test them independently.

好吧,您可以测试该代码吗? 不,您不能,因为它将向您的Firestore实例发送网络请求。 如果您的一项测试显示ListTile数据错误怎么办? 是因为数据库上的数据错误,还是因为您的构建函数无法正确处理数据,从而错误地构造了ListTile? 您不知道,因为您无法将两个部分分开进行独立测试。

Can you easily maintain that code?

您可以轻松维护该代码吗?

No, you can’t. As we just said, the build function is heavily coupled with Firestore. Not even mentioning readability, can you quickly identify what’s the purpose of the query Firestore.instance.collection(‘books’).snapshots()? How about if it was just getBooks() what’s easier to follow?

不,你不能。 如前所述, 构建功能与Firestore紧密结合。 甚至不提可读性,您能否快速确定查询Firestore.instance.collection('books')。snapshots()的目的是什么? 如果仅仅是getBooks(),那么更容易理解呢?

Right now you are probably thinking “Ok, if that’s so bad, then why would Firestore include this on their own samples?” and the answer is simple: Because they are meant to be just samples! Not an actual reference for future implementations.

现在,您可能会想: “好吧,如果真糟糕,那么为什么Firestore会在自己的样本中包含它?” 答案很简单:因为它们只是示例! 不是将来实现的实际参考。

They are there as a quick start, as something tiny where you can understand in a few seconds how easy it is to use Firestore and Flutter together, not that you actually should use it that way. Think of it as a TV ad for a new car, they show how fast you can go, or all the wild maneuvers you could do, but of course everyone knows that’s not how you are supposed to be driving.

它们是一个快速入门,因为它很小,您可以在几秒钟内了解将Firestore和Flutter一起使用是多么容易,而不是实际上应该以这种方式使用它。 可以将其视为新车的电视广告,它们显示出您可以走多快或可以进行所有野蛮动作,但当然每个人都知道这不是您应该如何驾驶。

Ok, so we already covered the problems and the reasons, let’s go to the solution.

好的,我们已经介绍了问题和原因,让我们开始解决方案。

(Solution)

There are many good ways to properly use Firestore with Flutter, specially if you are already using a State Management solution like Redux, BLoC, Provider with Change Notifier etc. but I want to show you a different one, a simple solution that I have been using for a while with very good results.

有很多很好的方法可以将Firestore与Flutter一起正确使用,特别是如果您已经在使用状态管理解决方案,例如Redux,BLoC,带有Change Notifier的Provider等,则尤其如此 。 但我想向您展示一个不同的解决方案,我已经使用了一段时间了,但效果很好。

This solution is nothing new in the realm of software engineering, it’s an adaptation I made to work with Flutter.

该解决方案在软件工程领域并不是什么新鲜事物,它是我与Flutter合作所做的改编。

It leverages Dependency Injection with the Repository Pattern. If you aren’t familiar with these concepts, don’t worry we will do a quick review on them, but I will still recommend you read more about at least Dependency Injection, as it’s a really powerful concept and deserves some special attention.

它利用依赖注入存储库模式 。 如果您不熟悉这些概念,请放心,我们将对其进行快速回顾,但是我仍然建议您至少阅读有关依赖注入的更多信息,因为它是一个非常强大的概念,值得特别注意。

The Repository Pattern is just an abstraction layer between your business logic and your data access logic. Some code will make this clearer:

存储库模式只是业务逻辑和数据访问逻辑之间的抽象层。 一些代码可以使这一点更加清晰:

First lets make a simple Book class:

首先让我们做一个简单的Book类:

class Book {
  Book(this.title, this.author);


  final String title;
  final String author;
}

So now we can query the database for books, using our Repository:

现在,我们可以使用我们的存储库在数据库中查询书籍:

class Repository {
  Stream<List<Book>> getBooks() {
    return Firestore.instance.collection('books').snapshots().map((snapshot) {
      return snapshot.documents
          .map((document) => Book(document['title'], document['author']))
          .toList();
    });
  }
}

With this Repository class we can now call getBooks and get a Stream of Books. The user of getBooks doesn’t have to know how it will return a Stream of Books, it just knows it will. That’s the contract getBooks offers to its users, the actual query implementation is encapsulated and abstracted, it could use Firestore or even another database. For the user of getBooks it doesn’t matter at all, it’s hidden and for a good reason.

有了这个Repository类,我们现在可以调用getBooks并获取BookStreamgetBooks的用户不必知道它将如何返回BookStream ,而是知道它会如何返回。 这就是getBooks向其用户提供的合同,实际的查询实现已封装和抽象,它可以使用Firestore甚至是另一个数据库。 对于getBooks的用户而言,这根本没有关系,它是隐藏的,并且有充分的理由。

Our Repository solution isn’t ready yet, even though it improves the testability of the build function, it uses Firestore.instance internally which makes testing the Repository class itself a lot harder, which is ironic at best. Also, using Firestore.instance will make our tests even harder since it’s a Singleton and we can’t easily control its behavior for each test case and isolate them, assuring that the result of one test won’t interfere with another.

我们的存储库解决方案尚未准备就绪,尽管它提高了构建功能的可测试性,但它内部使用Firestore.instance ,这使得对存储库类本身的测试变得更加困难,这具有讽刺意味。 另外,使用Firestore.instance将使我们的测试更加困难,因为它是一个Singleton,我们无法轻松地控制每个测试用例的行为并将其隔离,以确保一个测试的结果不会干扰另一个测试用例。

依赖注入 (Dependency Injection)

Dependency Injection is a programming style where, instead of creating dependencies internally, we create those externally and then inject them into any code that needs it. This architectural change enables higher testability and flexibility, as it allows for transparently swapping implementations, changing our algorithms behavior without actually modifying its code.

依赖注入是一种编程风格,其中,不是在内部创建依赖关系,而是在外部创建依赖关系,然后将其注入需要的任何代码中。 这种体系结构上的更改可实现更高的可测试性和灵活性,因为它允许透明地交换实现,更改我们的算法行为,而无需实际修改其代码。

If this sounds too abstract, fear not, Dependency Injection is a lot simpler to understand seeing actual code rather than reading definitions.

如果这听起来太抽象,请不要担心,依赖注入比看实际代码要容易得多,而不是阅读定义。

So in order to implement Dependency Injection to our sample, instead of calling Firestore.instance to get an instance of Firestore inside getBooks, we will inject that instance into the Repository class itself. The injection can be made by simply passing the instance to the class’s constructor and storing it for later usage under a field, but Dependency Injection libraries can be very handy as well, as we will see.

因此,为了对我们的示例实施依赖注入,而不是调用Firestore.instancegetBooks中获取Firestore的实例,我们将将该实例注入到Repository类本身中。 可以通过简单地将实例传递给类的构造函数并将其存储以供以后在字段下使用来进行注入,但是依赖注入库也非常方便,正如我们将看到的。

Once its dependencies are injected, the Repository class can’t know which instance of Firestore it’s dealing with, it could be a real one or a fake one, and that’s excellent for testing as all the network connections can be faked, with query results created on demand for each test case.

注入依赖项后, Repository类将无法知道它正在处理的是哪个Firestore实例,它可以是真实实例,也可以是假实例,这对于测试所有网络连接都可以被伪造并创建查询结果非常有用。根据每个测试用例的需求。

There are some ways of using Dependency Injection in Flutter, as of now (August 2020) Pub has a handful of packages but I recommend Provider as not only it helps with Dependency Injection but also integrates with Flutter as a “supercharged” InheritedWidget.

有一些方法可以在Flutter中使用依赖注入,到目前为止(2020年8月),Pub有少量软件包,但我推荐Provider,因为它不仅可以帮助依赖注入,还可以与Flutter集成为“ Supercharged” InheritedWidget

We will add Dependency Injection to our Repository by injecting a reference to a Firestore instance into the Repository’s constructor:

通过将对Firestore实例的引用注入到存储库的构造函数中,我们会将依赖项注入添加到存储 库中

class Repository {
  Repository(this._firestore) : assert(_firestore != null);


  final Firestore _firestore;


  Stream<List<Book>> getBooks() {
    return _firestore.collection('books').snapshots().map((snapshot) {
      return snapshot.documents
          .map((document) => Book(document['title'], document['author']))
          .toList();
    });
  }
}

Then we add Provider to our top level Widget (which we don’t have for this sample, but let’s pretend we have one named SampleApp), injecting the Repository into the entire Widget Tree. But when creating the Repository instance itself, we “manually” inject Firestore.instance into the Repository’s constructor.

然后,将Provider添加到我们的顶级小部件(此示例中没有,但我们假设我们有一个名为SampleApp ),将存储库注入到整个小部件树中。 但是,当自己创建存储库实例时,我们“手动”将Firestore.instance注入到存储库的构造函数中。

As you can see Dependency Injection is a programming pattern and you don’t need a Framework or a Library to apply it, but they do make it simpler and are very helpful.

如您所见,依赖注入是一种编程模式,不需要框架或库来应用它,但是它们确实使它更简单并且非常有帮助。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<Repository>(
      create: (_) => Repository(Firestore.instance)),
      child: MaterialApp(
        title: 'Sample App',
        theme: ThemeData(
        primaryColor: primaryColor,
        // And it continues as a traditional MaterialApp would…
}

With all this done, any Widgets can now use our brand new Repository. This includes the original sample that got us here, we just need to modify it a bit (and add a few performance optimizations as a bonus):

完成所有这些操作之后,任何小部件现在都可以使用我们全新的存储库 。 这包括将我们带到这里的原始示例,我们只需要对其进行一些修改(并添加一些性能优化作为奖励):

class BookList extends StatefulWidget {
  @override
  _BookListState createState() => _BookListState();
}


class _BookListState extends State<BookList> {
  Stream<List<Book>> _booksStream;


  @override
  void initState() {
    super.initState();
    // Here we get our injected Repository using Provider:
    _booksStream = context.read<Repository>().getBooks();
  }
 
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Book>>(
      stream: _booksStream,
      builder: (BuildContext context, AsyncSnapshot<List<Book>> snapshot) {
        if (snapshot.hasError)
          return Text('Error: ${snapshot.error}');
        switch (snapshot.connectionState) {
          case ConnectionState.waiting: return Text('Loading...');
          default:
            return ListView(            
              children: snapshot.data.map((Book book) {
                return ListTile(
                  title: Text(book.title),
                  subtitle: Text(book.author),
                );
              }).toList(),
            );
        }
      },
    );
  }
}

Let’s break down the modifications:

让我们分解一下修改:

  1. BookList is now a StatefulWidget. This is a performance optimization for the StreamBuilder. In the original sample we were creating the books’ Stream directly inside our build function, this is bad and should be avoided as the build function can be called by the framework multiple times, each creating a new Stream, unsubscribing from the old one and subscribing to the new. Depending on how your code is structured this can even lead to memory leaks.

    BookList现在是一个StatefulWidget 。 这是StreamBuilder的性能优化。 在原始示例中,我们直接在构建函数内部创建书籍的Stream ,这很不好,应该避免,因为框架可以多次调用构建函数,每次都创建一个新的Stream ,从旧的订阅中取消订阅到新的。 根据代码的结构,这甚至可能导致内存泄漏。

    By making

    通过制造

    BookList a StatefulWidget we can create our Stream once at initState and doesn’t matter how many times the build function is called, the same Stream instance will be used.

    列出一个StatefulWidget,我们可以在initState一次创建Stream ,而不论调用构建函数多少次,都将使用相同的Stream实例。

    Just keep in mind you might want to adapt this code if your App needs a more sophisticated

    请记住,如果您的应用程序需要更复杂的代码,则可能需要改编此代码

    Stream subscription logic, such as resubscribing in case of errors or if the Stream is closed.

    订阅逻辑,例如在发生错误或关闭时重新订阅。

  2. BookList doesn’t create an instance of Repository, we ask Provider to give us the one it holds internally using context.read<Repository>(). This way we can configure Provider with the instances we want to inject into BookList and all our other Widgets as well. They could be a real instance for our regular App or a fake instance for a Unit Test case.

    BookList不会创建Repository的实例,我们要求Provider使用context.read <Repository>()给我们提供它在内部拥有的一个实例。 这样,我们可以为Provider配置要注入BookList的实例以及所有其他Widget。 对于我们的常规应用程序,它们可能是真实实例,对于单元测试用例,它们可能是虚假实例。

  3. Injecting an instance of Repository into the Widget Tree root will enforce that all our Widgets use the same instance, this is something close to a Singleton, but without the drawbacks. Don’t worry, Provider doesn’t limit you to a Singleton-only injection. If you need to substitute that instance to another one you can.

    存储库的实例注入到Widget Tree的根目录中将强制我们所有的Widget使用相同的实例,这接近Singleton,但是没有缺点。 不用担心,Provider不会将您限制为仅Singleton注入。 如果您需要将该实例替换为另一个实例,则可以。

奖励积分: (Bonus points:)

  • The Book class we used is nothing more than a Value Object. So I suggest you read more about them and use built_value to auto-generate code, saving you lots of time getting rid of boring boilerplate code. With built_value we can directly deserialize data from Firestore to a Book object instance.

    我们使用的Book类不过是一个Value Object。 因此,我建议您阅读有关它们的更多信息,并使用built_value自动生成代码,从而节省大量时间来摆脱无聊的样板代码。 使用built_value,我们可以直接将数据从Firestore反序列化为Book对象实例。

  • If you have been paying close attention, our code basically uses the Repository class for State Management. It’s composed of functions that return Stream and those Streams, paired with a StreamBuilder, automatically update our App’s State. If you are thinking “wait, isn’t this just a Simple BLoC”? Yep, it is. Not that we intended to but, it’s a great coincidence that Firestore’s automatic Stream updates can be encapsulated with the Repository Pattern, thus turning out to be a Simple BLoC!

    如果您一直在密切注意,我们的代码基本上将Repository类用于State Management。 它由返回Stream的函数和与StreamBuilder配对的Streams 组成,可自动更新应用程序的状态。 如果您正在考虑“等等,这不只是简单的BLoC”吗? 是的,是的。 不是我们想要的,但是,Firestore的自动Stream更新可以与Repository Pattern封装在一起是一个很大的巧合,因此证明它是一个简单的BLoC!

    (By Simple BLoC I mean the Pattern where we have functions returning

    (通过简单的BLoC,我指的是其中有函数返回的模式

    Streams, instead of the traditional full-fledged BLoC where we have Sinks as inputs and Streams as outputs. This was an early definition of the BLoC Pattern, but some packages popularized BLoC in a slightly different form).

    Streams ,而不是传统的成熟BLoC,在这里我们将接收器作为输入,将Streams作为输出。 这是BLoC模式的早期定义,但有些软件包以略有不同的形式推广了BLoC。

    Keep in mind the

    请记住

    Repository is only meant to be a data-access abstraction layer. For a simple CRUD implementation it can act as a State Management object as well, but if you need additional business logic, don’t do it in the Repository as you would in a BLoC.

    储存库仅旨在作为数据访问抽象层。 对于简单的CRUD实现,它也可以充当状态管理对象,但是如果您需要其他业务逻辑,则不要像在BLoC中那样在存储库中进行操作

  • Our Repository is a very simple one, and this was intentional, as to not deviate from the main subject. Also the Repository Pattern can be found in many forms in the wild, like the ones more focused on DDD or as a simple DAO, so given the differences in complexity, I prefer leaving the implementation choice to you, as I can’t say what suits better your project.

    我们的存储库是一个非常简单的存储库 ,这是有意的,以免偏离主要主题。 另外,Repository Pattern可以在野外以多种形式找到,例如更着重于 DDD或作为简单DAO的形式 ,因此鉴于复杂性的差异,我宁愿将实现选择留给您,因为我不能说什么更适合您的项目。

  • This architecture can be expanded so think about how it could be incorporated into your own App, for example, you could inject Firebase Auth into the Repository class as well so you could query by user ID, or even inject Firebase Auth into your entire App so you could make Authentication and Authorization logic highly testable!

    可以扩展此体系结构,因此请考虑如何将其合并到自己的App中,例如,您也可以将Firebase Auth注入到Repository类中,以便可以通过用户ID查询,甚至可以将Firebase Auth注入到整个App中,您可以使认证和授权逻辑具有很高的可测试性!

  • If you have lots of Firestore documents, your App may get too many updates to deal with, which might severely impact your performance. In that case, consider adding pagination or other network performance improvements to your Repository.

    如果您有许多Firestore文档,则您的应用程序可能会处理太多更新,可能会严重影响您的性能。 在这种情况下,请考虑向您的存储库添加分页或其他网络性能改进

    That’s what a data-access abstraction layer is all about, hiding implementation details and decoupling complex networking logic.

    这就是数据访问抽象层的全部作用,它隐藏了实现细节并解耦了复杂的网络逻辑。

  • Take a look at Mockito so you can easily create fake test instances (Mocks) and save yourself some precious time.

    看一下Mockito,以便您可以轻松地创建假的测试实例(模拟)并节省一些宝贵的时间。

结语 (Wrapping up)

Firestore’s official sample is a great quick start, it succinctly shows how to get up and running and how amazing and powerful the library can be, but it’s architecture shouldn’t be followed by production-ready Apps. By leveraging Dependency Injection and the Repository Pattern we were able to build a much more maintainable and testable solution.

Firestore的官方示例是一个很好的快速入门,它简洁地显示了如何启动和运行以及该库可以具有多么惊人和强大的功能,但是它的体系结构不应该由可用于生产的Apps来遵循。 通过利用依赖注入和存储库模式,我们能够构建更加可维护和可测试的解决方案。

https://www.twitter.com/FlutterComm

https://www.twitter.com/FlutterComm

翻译自: https://medium.com/flutter-community/flutter-firestore-you-may-be-using-it-wrong-b56fa689e489

flutter 包错误

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值