桌面应用程序 架构_关于该架构的全部内容:探索不同的架构模式以及如何在您的应用程序中使用它们

桌面应用程序 架构

Kriptofolio应用程序系列-第3部分 (Kriptofolio app series - Part 3)

The most important thing to focus on when starting to build a new app is architecture. The biggest mistake you can make is to go with no architecture style at all.

开始构建新应用程序时,最重要的事情是架构。 您可能会犯的最大错误是完全没有建筑风格。

The topic of architecture choice has been quite controversial for the Android community in recent years. Even Google decided to get involved. In 2017 they proposed their own approach of standardized architecture by releasing Android Architecture Components. It was intended to make developers’ lives easier.

近年来,架构选择这一话题在Android社区中引起了很大争议。 甚至Google都决定参与其中。 在2017年,他们通过发布Android Architecture Components提出了自己的标准化架构方法。 目的是使开发人员的生活更轻松。

In this post, I am first going to discuss why we need to architect our apps. We will cover what options we have. Then we are going to learn how to do that. Rather than reinventing the wheel, we will use guidelines provided by the Android team.

在本文中,我将首先讨论为什么我们需要设计我们的应用程序。 我们将介绍我们有哪些选择。 然后,我们将学习如何做到这一点。 除了重新发明轮子之外,我们将使用Android团队提供的指南。

This post was the hardest one for me to write because of my own lack of knowledge about it. First I had to study the topic of architecture really well to see the bigger picture. Now I am ready to share my findings with you.

这篇文章对我来说是最难写的,因为我自己对此知识不足。 首先,我必须很好地研究建筑主题才能看到更大的图景。 现在,我准备与您分享我的发现。

系列内容 (Series content)

您为什么要关心应用架构? (Why should you care about app architecture?)

Usually when you start working with Android you end up writing most of the core business logic in activities or fragments. This happens to all new Android developers, including myself. All short tutorials and all samples suggest doing that. And actually, for small apps created for explanation, that works good enough.

通常,当您开始使用Android时,最终会在活动或片段中编写大多数核心业务逻辑。 所有新的Android开发人员(包括我自己)都会遇到这种情况。 所有简短的教程和所有示例均建议这样做。 实际上,对于为解释而创建的小型应用程序,它已经足够好了。

However, try to do that on a real app which is constantly changing according to the users’ needs and expanding it with new features. Soon you will see that your coding experience is getting more and more painful. Everything becomes managed by so-called “God Classes” like activities or fragments. These have so many lines of code that you get lost easily.

但是,请尝试在真实的应用程序中执行此操作,该应用程序会根据用户的需求不断变化,并使用新功能进行扩展。 很快,您会发现您的编码经验变得越来越痛苦。 一切都由活动或片段之类的所谓“神类”管理。 这些代码行太多,您很容易迷路。

Basically all your code starts to look like spaghetti where everything is mixed up. All the parts depend on each other. Then when new changes are required by the business, you are left with no choice but to rebuild the entire project. Also that’s the point where architecture questions start to appear.

基本上,您的所有代码开始看起来像意大利面条,在这里所有东西都混合在一起。 所有部分都相互依赖。 然后,当企业需要进行新的更改时,您别无选择,只能重建整个项目。 这也是架构问题开始出现的地方。

有没有更好的方法来组织代码? (Is there a better way to structure your code?)

Of course there is! The key for high quality code is to follow the SOLID principles. I talked about this in my previous post (not without a reason). You should also apply some architecture pattern for separation of concerns. In fact, separation of concerns should be your ultimate goal. It is the most significant point which indicates code quality. There are quite a few patterns out there for app architectures. The most well known are the classic three tier architectures such as:

当然有! 高质量代码的关键是遵循SOLID原则。 我在上一篇文章中谈到了这一点(并非没有原因)。 您还应该应用一些体系结构模式来分离关注点。 实际上,关注点分离应该是您的最终目标。 这是最重要的一点,它指示代码质量。 应用程序架构有很多模式。 最著名的是经典的三层体系结构,例如:

  • MVC: Model-View-Controller

    MVC:模型-视图-控制器
  • MVP: Model-View-Presenter

    MVP:模型视图呈现器
  • MVVM: Model-View-ViewModel

    MVVM:模型-视图-视图模型

All these patterns represent the main similar idea — to structure your project’s code in a way that it is separated by the different generic layers. Every layer has its own responsibility. That’s why your project becomes modular: separated code parts are more testable, and your app is flexible enough for continuous changes.

所有这些模式都代表着主要的相似思想-以不同的通用层分开的方式构造项目的代码。 每层都有自己的责任。 这就是您的项目成为模块化的原因:分离的代码部分更易于测试,并且您的应用程序足够灵活以进行连续更改。

If we talk about each pattern individually, the topic becomes too broad. I am only going to introduce you to each one just so you can understand main differences.

如果我们单独讨论每种模式,那么话题将变得太宽泛。 我仅向您介绍每个人,以便您了解主要差异。

模型-视图-控制器(MVC)模式 (The Model-View-Controller (MVC) pattern)

This pattern was the first iteration Android app architecture took back in the old times. It suggests that you separate your code to 3 different layers:

这种模式是Android应用架构在旧时代的第一次迭代。 建议您将代码分为3个不同的层:

Model — the data layer. Responsible for handling the business logic and communication with the network and database layers.

模型-数据层。 负责处理业务逻辑以及与网络和数据库层的通信。

View — the user interface (UI) layer. It’s a simple visualization of the data from the Model.

视图-用户界面(UI)层。 这是来自模型的数据的简单可视化。

Controller — the logic layer, gets notified of the user behavior and updates the Model as needed.

控制器-逻辑层,获得用户行为的通知并根据需要更新模型。

This is the MVC schema. In it we can see that both the Controller and the View depend on the Model. The Controller updates the data. The View gets the data. However, the Model is separated and could be tested independently of the UI.

这是MVC架构。 在其中,我们可以看到Controller和View都依赖于Model。 控制器更新数据。 视图获取数据。 但是,模型是分离的,可以独立于UI进行测试。

There are a few approaches of how to apply the MVC pattern. It’s quite confusing.

有几种方法可以应用MVC模式。 这很令人困惑。

One is when activities and fragments act like the Controller. They are in charge of processing the data and updating the views. The problem with this architecture approach is that activities and fragments can become quite large and very difficult to test.

一种是活动和片段的行为类似于控制器。 他们负责处理数据和更新视图。 这种体系结构方法的问题在于活动和片段可能变得很大,并且很难测试。

Another approach which seems more logical (and correct) is where activities and fragments should be the Views in the MVC world. The Controllers should be separate classes that don’t extend or use any Android class. Same for the Models.

似乎更合乎逻辑(且更正确)的另一种方法是,活动和片段应该是MVC世界中的“视图”。 控制器应该是不扩展或不使用任何Android类的单独的类。 型号相同。

Anyway if you investigate more about MVC, you will find out that when applied to an Android project, even in the correct way the code layers depend on each other. That’s why I would not recommend that you use it anymore for your next Android app.

无论如何,如果您对MVC进行更多研究,就会发现,即使以正确的方式将代码层彼此依赖,也可以将其应用于Android项目。 这就是为什么我不建议您再将其用于下一个Android应用程序的原因。

模型视图呈现器(MVP)模式 (The Model-View-Presenter (MVP) pattern)

After the first approach, which didn’t work, Android developers moved on and tried to use one of the most popular architectural patterns — MVP. This pattern represents a second iteration of architecture choice. This pattern became widely used and is still a recommended one. For anybody who starts Android development, it is easy to learn. Let’s have a look at its 3 separate layers roles:

在第一种方法无效后,Android开发人员继续前进,并尝试使用一种最受欢迎​​的体系结构模式-MVP。 此模式代表体系结构选择的第二次迭代。 这种模式已被广泛使用,仍然是推荐的模式。 对于任何开始Android开发的人来说,它都很容易学习。 让我们看看它的3个单独的层角色:

Model — the data layer, which is the same as on MVC pattern. Responsible for handling the business logic and communication with the network and database layers.

模型—数据层,与MVC模式相同。 负责处理业务逻辑以及与网络和数据库层的通信。

View — the user interface (UI) layer. Displays the data and notifies the Presenter about user actions.

视图-用户界面(UI)层。 显示数据并通知演示者用户操作。

Presenter — retrieves the data from the Model, applies the UI logic and manages the state of the View, decides what to display, and reacts to user input notifications from the View. This is essentially the controller from MVC except that it is not at all tied to the View, just an interface.

Presenter —从模型中检索数据,应用UI逻辑并管理View的状态,决定要显示的内容,并对View的用户输入通知做出React。 本质上,这是MVC的控制器,只是它根本不与View绑定,而只是与接口绑定。

MVP schema shows that the View and the Presenter are closely related. They need to have a reference to one another. Their relationship is defined in a Contract interface class.

MVP模式显示View和Presenter是紧密相关的。 他们需要互相参考。 它们的关系在Contract接口类中定义。

This pattern has one significant but controllable disadvantage. The Presenter tends to expand to a huge all-knowing class if you are not careful enough and don’t break your code according to single responsibility principle. However, generally speaking, the MVP pattern offers a very good separation of concerns. It could be your main choice for your project.

这种模式具有一个明显但可控制的缺点。 如果您不够谨慎并且不按照单一职责原则破坏代码,Presenter往往会扩展为一个全知的类。 但是,总的来说,MVP模式提供了很好的关注点分离。 这可能是您的项目的主要选择。

Model-View-ViewModel(MVVM)模式 (The Model-View-ViewModel (MVVM) pattern)

MVVM pattern is the third iteration of the approach. It became the architecture pattern recommended by Android team with Android Architecture Components’ release. That’s why we will be focusing on learning this pattern most of all. Also I will be using it for “My Crypto Coins” app. As before, let’s take a look at its separate code layers:

MVVM模式是该方法的第三次迭代。 它成为Android团队随Android Architecture Components发布而推荐的架构模式。 这就是为什么我们将最着重于学习这种模式的原因。 我还将在“我的加密货币”应用程序中使用它。 和以前一样,让我们​​看一下其单独的代码层:

Model — abstracts the data source. The ViewModel works with the Model to get and save the data.

模型—抽象数据源。 ViewModel与Model一起使用以获取和保存数据。

View — that informs the ViewModel about the users’ actions.

视图-通知ViewModel用户的操作。

ViewModel — exposes streams of data relevant to the View.

ViewModel —公开与View相关的数据流。

The difference compared to MVP pattern is that, in MVVM, the ViewModel does not hold a reference to the View as it is with the Presenter. In MVVM, the ViewModel exposes a stream of events to which various Views can bind. On the other hand, in the MVP case, the Presenter directly tells the View what to display. Let’s take a look at the MVVM schema:

与MVP模式相比,区别在于,在MVVM中,ViewModel不像对Presenter一样保留对View的引用。 在MVVM中,ViewModel公开了各种View可以绑定到的事件流。 另一方面,在MVP情况下,演示者直接告诉View显示什么。 让我们看一下MVVM模式:

In MVVM the View has a reference to ViewModel. ViewModel has no information about the View. There is a many-to-one relationship between View and ViewModel.

在MVVM中,视图具有对ViewModel的引用。 ViewModel没有有关View的信息。 View和ViewModel之间存在多对一的关系。

MVC,MVP和MVVM的比较 (Comparison of MVC vs MVP vs MVVM)

Here is a table which sums up all the patterns I talked about:

这是一张表格,总结了我所谈论的所有模式:

As you may have noticed, MVC is not so good compared to MVP and MVVM when building a modular and testable modern app. But each pattern has its own advantages and disadvantages. It is a good choice if it exactly fits your needs. I suggest that you investigate and learn more about all these patterns as it is worth it.

您可能已经注意到,在构建模块化且可测试的现代应用程序时,与MVP和MVVM相比,MVC并不是很好。 但是每种模式都有其自身的优点和缺点。 如果它完全适合您的需求,那么这是一个不错的选择。 我建议您调查并了解所有这些模式,因为这是值得的。

In the meantime, I will continue my project with the trending pattern in 2018, which is also pushed by Google — MVVM.

同时,我将以2018年的趋势模式继续我的项目,该模式也由Google(MVVM)推动。

Android体系结构组件 (Android Architecture Components)

If you’re familiar with the Android application lifecycle, you know what a headache it can be to build an app that avoids all data flow problems and persistence and stability issues which usually appear during configuration change.

如果您熟悉Android应用程序的生命周期,那么您会知道构建一个可避免通常在配置更改期间出现的所有数据流问题以及持久性和稳定性问题的应用程序会令人头疼。

In 2017, the Android team decided we’d struggled enough. They assumed responsibility and introduced the Android Architecture Components framework. This finally lets you solve all these issues without complicating your code or even applying hacks to it.

在2017年,Android团队认为我们已经足够挣扎。 他们承担了责任,并介绍了Android Architecture Components框架。 最终,您可以解决所有这些问题,而不必使代码复杂化,甚至无需对其进行破解。

Android Architecture Components is a collection of libraries that helps you design robust, testable, and maintainable apps. At the current moment when I am writing this blog post, it consists of these components:

Android Architecture Components是一组库,可帮助您设计健壮,可测试和可维护的应用程序。 目前,在我撰写此博客文章时,它包含以下组件:

  • Data Binding — declaratively bind observable data to UI elements

    数据绑定 -以声明方式将可观察的数据绑定到UI元素

  • Lifecycles — manage your activity and fragment lifecycles

    生命周期 -管理活动和片段生命周期

  • LiveData — notify views when underlying database changes

    LiveData —在基础数据库更改时通知视图

  • Navigation — handle everything needed for in-app navigation

    导航 -处理应用内导航所需的一切

  • Paging — gradually load information on demand from your data source

    分页 —逐步从数据源中按需加载信息

  • Room — fluent SQLite database access

    —流畅SQLite数据库访问

  • ViewModel — manage UI-related data in a lifecycle-conscious way

    ViewModel —以生命周期感知的方式管理与UI相关的数据

  • WorkManager — manage your Android background jobs

    WorkManager —管理您的Android后台作业

With the help of the Android Architecture Components we are going to implement MVVM architecture pattern in My Crypto Coins app following this diagram:

借助Android Architecture Components,我们将按照下图在My Crypto Coins应用程序中实现MVVM体系结构模式:

It’s a recommended architecture by Google. It shows how all the modules should interact with one another. Next we’ll be covering only the specific Android Architecture Components that we will use in our project.

这是Google推荐的架构。 它显示了所有模块如何相互交互。 接下来,我们将仅讨论将在项目中使用的特定Android体系结构组件。

整理您的源文件 (Organizing your source files)

Before starting development we should consider how we should organize our project’s source files. We can not leave this question unanswered, as later we would have a messy structure hard to understand and modify.

在开始开发之前,我们应该考虑如何组织项目的源文件。 我们不能不回答这个问题,因为稍后我们将有一个难以理解和修改的混乱结构。

There are several ways to do it. One is to organize by component category. For example, all activities goes to their own folder, all adapters go to their folder and so on.

有几种方法可以做到这一点。 一种是按组件类别进行组织。 例如,所有活动都转到其自己的文件夹,所有适配器都转到其文件夹,依此类推。

Another way would be to organize everything by app features. For example, search and add crypto in all crypto currencies list feature goes to its own addsearchlist folder. The main idea is that you need to do it in some specific way instead of having everything placed randomly. I use some kind of mix of both of these.

另一种方法是通过应用程序功能来组织所有内容。 例如,在所有加密货币列表功能中搜索和添加加密都转到其自己的addsearchlist文件夹。 主要思想是您需要以某种特定方式进行操作,而不是将所有内容随机放置。 我将两者混合使用。

Besides the project’s folder structure, you should consider applying some rules for naming project files. For example, when naming Android classes you should define the class purpose clearly in the name.

除了项目的文件夹结构之外,您还应该考虑应用一些规则来命名项目文件。 例如,命名Android类时,应在名称中明确定义类的用途。

视图模型 (ViewModel)

For the start of our app architecture development, first we are going to create the ViewModel. View models are objects that provide data for UI components and survive configuration changes.

对于我们应用程序体系结构开发的开始,首先我们将创建ViewModel。 视图模型是为UI组件提供数据并可以保留配置更改的对象。

You can use a ViewModel to retain data across the entire lifecycle of an activity or a fragment. Activities and fragments are short-lived objects. They are created and destroyed frequently as a user interacts with an app. A ViewModel is also better suited to managing tasks related to network communication, as well as data manipulation and persistence.

您可以使用ViewModel在活动或片段的整个生命周期中保留数据。 活动和片段是短暂的对象。 它们在用户与应用程序交互时经常创建和销毁。 ViewModel也更适合于管理与网络通信有关的任务,以及数据操纵和持久性。

As example now lets create a ViewModel for MainListFragment to separate the UI data from it.

作为示例,现在让我们为MainListFragment创建一个ViewModel来将UI数据MainListFragment分离。

class MainViewModel : ViewModel() {
    ...
}

Then obtain the ViewModel with single line of code.

然后使用单行代码获取ViewModel。

class MainListFragment : Fragment() {
    ...
    private lateinit var viewModel: MainViewModel
    ...
    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()

        // Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        ...
    }
    ...
}

Basically that’s it, congratulations! 🙂 Let’s move on.

基本上就是这样,恭喜! 🙂让我们继续前进。

实时数据 (LiveData)

LiveData is an observable data holder class. It follows the observer pattern. LiveData is lifecycle-aware. This means that it only updates app component (activity, fragment, etc.) observers that are in an active lifecycle state.

LiveData是可观察的数据持有者类。 它遵循观察者模式 。 LiveData具有生命周期意识。 这意味着它仅更新处于活动生命周期状态的应用程序组件(活动,片段等)观察者。

LiveData class returns the latest value of the data. When data changes it returns the updated value. LiveData is best suited with ViewModel.

LiveData类返回数据的最新值。 数据更改时,它将返回更新的值。 LiveData最适合ViewModel。

We will use LiveData together with ViewModel like this:

我们将LiveData与ViewModel一起使用,如下所示:

...
class MainViewModel : ViewModel() {

    private val liveData = MutableLiveData<ArrayList<Cryptocurrency>>()
    val data: LiveData<ArrayList<Cryptocurrency>>
        get() = liveData

    init {
        val tempData = ArrayList<Cryptocurrency>()

        val btc:Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82)
        val eth:Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32)

        tempData.add(btc)
        tempData.add(eth)

        liveData.value = tempData
    }
}

Observe data on the ViewModel, exposed as LiveData:

观察在ViewModel上显示为LiveData的数据:

...
class MainListFragment : Fragment() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var recyclerAdapter: MainRecyclerViewAdapter

    private lateinit var viewModel: MainViewModel

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()

        // Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

        // Observe data on the ViewModel, exposed as a LiveData
        viewModel.data.observe(this, Observer { data ->
            // Set the data exposed by the LiveData
            if (data != null) {
                recyclerAdapter.setData(data)
            }
        })
    }
    ...
}

Browse the repository at this point in the history here.

此处浏览历史记录中的存储库。

数据绑定 (Data Binding)

Data Binding Library was created to remove boilerplate code needed to connect to XML layouts.

创建了数据绑定库以删除连接到XML布局所需的样板代码。

To use Data Binding in your Kotlin projects, you will need to turn on support for annotation processors with the kapt compiler plugin. Also add data binding block to the Android configuration gradle file:

要在Kotlin项目中使用数据绑定,您需要使用kapt编译器插件打开对注释处理器的支持。 还将数据绑定块添加到Android配置gradle文件中:

...
apply plugin: 'kotlin-kapt'

android {
    ...
    dataBinding {
        enabled = true
    }
}
...

To use data binding generated classes, we need to put all the view code in <layout> tags. The most powerful concept of data binding is that we can bind some data class to an xml layout and item properties to fields directly.

要使用数据绑定生成的类,我们需要将所有视图代码放入<layo ut>标记中。 数据绑定的最强大概念是我们可以将某些数据类绑定到xml布局,并将项目属性直接绑定到字段。

<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="cryptocurrency"
            type="com.baruckis.mycryptocoins.data.Cryptocurrency" />
    </data>
  
    ...      

            <android.support.v7.widget.AppCompatTextView
                android:id="@+id/item_name"
                style="@style/MainListItemPrimeText"
                android:layout_marginEnd="@dimen/main_cardview_list_item_text_between_margin"
                android:layout_marginStart="@dimen/main_cardview_list_item_inner_margin"
                android:text="@{cryptocurrency.name}"
                android:textAlignment="viewStart"
                app:layout_constraintBottom_toTopOf="@+id/item_amount_symbol"
                app:layout_constraintEnd_toStartOf="@+id/guideline1_percent"
                app:layout_constraintStart_toEndOf="@+id/item_image_icon"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_chainStyle="spread"
                tools:text="@string/sample_text_item_name" />

     ...
</layout>

RecyclerView adapter with data biding will look like this:

具有数据出价功能的RecyclerView适配器如下所示:

class MainRecyclerViewAdapter() : RecyclerView.Adapter<MainRecyclerViewAdapter.BindingViewHolder>() {

    private lateinit var dataList: ArrayList<Cryptocurrency>

    fun setData(newDataList: ArrayList<Cryptocurrency>) {
        dataList = newDataList
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = FragmentMainListItemBinding.inflate(inflater, parent, false)

        return BindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) = holder.bind(dataList[position])

    override fun getItemCount(): Int = dataList.size

    ...

    inner class BindingViewHolder(var binding: FragmentMainListItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(cryptocurrency: Cryptocurrency) {
            binding.cryptocurrency = cryptocurrency

            binding.itemRanking.text = String.format("${cryptocurrency.rank}")
            ...
            binding.executePendingBindings()
        }
    }
}

At last no more writing findViewById 🙂 Browse the repository at this point in the history here.

最后,不再需要编写findViewById此处的历史记录中浏览存储库。

房间 (Room)

Our app needs to store persistent data of different cryptocurrencies that the user holds. This should be stored inside the local database that is kept inside the Android device privately.

我们的应用程序需要存储用户持有的不同加密货币的持久数据。 这应该存储在本地数据库中,该本地数据库私下保存在Android设备中。

For storing structured data in a private database we will use a SQLite database. This is often the best choice.

为了将结构化数据存储在私有数据库中,我们将使用SQLite数据库。 这通常是最佳选择。

In order to create the SQLite database for our app we will use Room. Room is a persistence library made by the Android team which is a wrapper above SQLite. It is an abstraction layer that removes much of the boilerplate code you need to interact with SQLite. It also adds compile-time checking of your SQL queries.

为了为我们的应用程序创建SQLite数据库,我们将使用Room。 Room是由Android团队制作的持久性库,是SQLite之上的包装器。 它是一个抽象层,删除了与SQLite交互所需的许多样板代码。 它还添加了对SQL查询的编译时检查。

The best way to think of it is as an ORM (Object Relational Mapper) tool designed to automatically generate glue code to map between your object instances and rows in your database.

想到它的最佳方法是设计为自动生成粘合代码以在对象实例和数据库中的行之间进行映射的ORM(对象关系映射器)工具。

There are basically 3 major components in Room:

会议室中基本上有3个主要组成部分:

  1. Entity — this component represents a class that holds a database row. For each entity, a database table is created to hold the items.

    实体-此组件表示一个保存数据库行的类。 对于每个实体,将创建一个数据库表来保存项目。
  2. DAO (Data Access Object) — the main component which is responsible for defining the methods that access the database.

    DAO(数据访问对象)—主要组件,负责定义访问数据库的方法。
  3. Database — a component which is a holder class that uses annotation to define the list of entities, the list of DAOs, and database version and serves as the main access point for the underlying connection.

    数据库—是持有人类的一个组件,它使用注释定义实体列表,DAO列表和数据库版本,并用作基础连接的主要访问点。

Let’s follow these simple steps to setup Room in our My Crypto Coins app:

让我们按照以下简单步骤在“我的加密货币”应用中设置“房间”:

  1. Create an Entity.

    创建一个实体。
@Entity
data class Cryptocurrency(val name: String,
                          val rank: Short,
                          val amount: Double,
                          @PrimaryKey
                          val symbol: String,
                          val price: Double,
                          val amountFiat: Double,
                          val pricePercentChange1h: Double,
                          val pricePercentChange7d: Double,
                          val pricePercentChange24h: Double,
                          val amountFiatChange24h: Double)

Add some extra information to tell Room about its structure in the database.

添加一些额外的信息,以告知Room其在数据库中的结构。

2. Create the DAO.

2.创建DAO。

@Dao
interface MyCryptocurrencyDao {

    @Query("SELECT * FROM Cryptocurrency")
    fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>>

    @Insert
    fun insertDataToMyCryptocurrencyList(data: List<Cryptocurrency>)
}

For the start, we are going to create a DAO which only allows us to retrieve records from the table that we have created with Entity and also to insert some sample data.

首先,我们将创建一个DAO,该DAO仅允许我们从使用Entity创建的表中检索记录,还可以插入一些示例数据。

3. Create and setup the Database.

3.创建并设置数据库。

It is important to say that the Database instance should ideally be built only once per session. The one way to achieve this would be to use a Singleton pattern.

重要的是要说数据库实例最好在每个会话中仅构建一次。 实现此目的的一种方法是使用Singleton模式。

@Database(entities = [Cryptocurrency::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract fun myCryptocurrencyDao(): MyCryptocurrencyDao


    // The AppDatabase a singleton to prevent having multiple instances of the database opened at the same time.
    companion object {

        // Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
        @Volatile
        private var instance: AppDatabase? = null

        // For Singleton instantiation.
        fun getInstance(context: Context): AppDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

        // Creates and pre-populates the database.
        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                    // Prepopulate the database after onCreate was called.
                    .addCallback(object : Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            // Insert the data on the IO Thread.
                            ioThread {
                                getInstance(context).myCryptocurrencyDao().insertDataToMyCryptocurrencyList(PREPOPULATE_DATA)
                            }
                        }
                    })
                    .build()
        }

        // Sample data.
        val btc: Cryptocurrency = Cryptocurrency("Bitcoin", 1, 0.56822348, "BTC", 8328.77, 4732.60, 0.19, -10.60, 0.44, 20.82)
        val eth: Cryptocurrency = Cryptocurrency("Etherium", 2, 6.0, "ETH", 702.99, 4217.94, 0.13, -7.38, 0.79, 33.32)

        val PREPOPULATE_DATA = listOf(btc, eth)

    }

}
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()

// Utility method to run blocks on a dedicated background thread, used for io/database work.
fun ioThread(f : () -> Unit) {
    IO_EXECUTOR.execute(f)
}

As you see on initial run, the database will be prepopulated with some sample data just for testing purposes.

正如您在初次运行时所看到的那样,该数据库将预先填充一些示例数据,仅用于测试目的。

4. EXTRA step. Create the Repository.

4. EXTRA步骤。 创建存储库。

The Repository is not part of the Architecture Components libraries. It is a suggested best practice for code separation and architecture.

该存储库不是体系结构组件库的一部分。 这是建议的代码分离和体系结构最佳实践。

It stands as a single source of truth for all app data in case you have to manage multiple data sources.

万一您必须管理多个数据源,它可以作为所有应用程序数据的单一事实来源。

class MyCryptocurrencyRepository private constructor(
        private val myCryptocurrencyDao: MyCryptocurrencyDao
) {

    fun getMyCryptocurrencyLiveDataList(): LiveData<List<Cryptocurrency>> {
        return myCryptocurrencyDao.getMyCryptocurrencyLiveDataList()
    }

    companion object {

        // Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
        @Volatile
        private var instance: MyCryptocurrencyRepository? = null

        // For Singleton instantiation.
        fun getInstance(myCryptocurrencyDao: MyCryptocurrencyDao) =
                instance ?: synchronized(this) {
                    instance
                            ?: MyCryptocurrencyRepository(myCryptocurrencyDao).also { instance = it }
                }
    }
}

We are going to use this repository in our ViewModel.

我们将在ViewModel中使用此存储库。

class MainViewModel(myCryptocurrencyRepository: MyCryptocurrencyRepository) : ViewModel() {

    val liveData = myCryptocurrencyRepository.getMyCryptocurrencyLiveDataList()
}

Our Fragment code also evolves.

我们的片段代码也在不断发展。

class MainListFragment : Fragment() {

    ...

    private lateinit var viewModel: MainViewModel

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {

        super.onActivityCreated(savedInstanceState)

        setupList()
        subscribeUi()
    }

    ...

    private fun subscribeUi() {

        val factory = InjectorUtils.provideMainViewModelFactory(requireContext())
        // Obtain ViewModel from ViewModelProviders, using this fragment as LifecycleOwner.
        viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)

        // Update the list when the data changes by observing data on the ViewModel, exposed as a LiveData.
        viewModel.liveData.observe(this, Observer<List<Cryptocurrency>> { data ->
            if (data != null && data.isNotEmpty()) {
                emptyListView.visibility = View.GONE
                recyclerView.visibility = View.VISIBLE
                recyclerAdapter.setData(data)
            } else {
                recyclerView.visibility = View.GONE
                emptyListView.visibility = View.VISIBLE
            }
        })

    }

}

Because our ViewModel class now has a constructor which is not empty anymore, we need to implement a provider factory pattern. This will be passed to the ViewModelProviders.of() method as the second parameter.

因为我们的ViewModel类现在具有一个不再为空的构造函数,所以我们需要实现提供者工厂模式。 这将作为第二个参数传递给ViewModelProviders.of()方法。

object InjectorUtils {

    fun provideMainViewModelFactory(
            context: Context
    ): MainViewModelFactory {
        val repository = getMyCryptocurrencyRepository(context)
        return MainViewModelFactory(repository)
    }

    private fun getMyCryptocurrencyRepository(context: Context): MyCryptocurrencyRepository {
        return MyCryptocurrencyRepository.getInstance(
                AppDatabase.getInstance(context).myCryptocurrencyDao())
    }
}
class MainViewModelFactory(private val repository: MyCryptocurrencyRepository) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(repository) as T
    }
}

Browse the repository at this point in the history here.

此处浏览历史记录中的存储库。

最后的想法 (Final thoughts)

Design architectures, that we discussed in this part, should be used as informed guidelines but not a hard rules. I did not wanted to go too much into detail on each topic. With Android Architecture Components we had a look at the coding process. Have in mind that there is lot more to learn on each component individually and I advise you to do that.

我们在本部分中讨论的设计体系结构应被用作知情指导,而不是硬性规则。 我不想在每个主题上都过分详细。 借助Android Architecture Components,我们了解了编码过程。 请记住,每个组件上还有很多要学习的知识,我建议您这样做。

Let’s summarize everything that we manage to make already:

让我们总结一下我们已经完成的所有工作:

  • In My Crypto Coins app, every separate screen has its own ViewModel. This will survive any configuration change and protect the user from any data loss.

    在“我的加密货币”应用程序中,每个单独的屏幕都有其自己的ViewModel。 这将在任何配置更改后仍然存在,并保护用户免受任何数据丢失。
  • The App’s user interface is a reactive type. This means it will update immediately when the data changes in the back-end. That is done with the help of LiveData.

    该应用程序的用户界面是React型。 这意味着它将在后端中的数据更改时立即更新。 这是在LiveData的帮助下完成的。
  • Our project have less code as we bind to the variables in our code directly using Data Binding.

    由于我们直接使用数据绑定将代码绑定到代码中的变量,因此项目的代码更少。
  • Finally, our app stores user data locally inside the device as a SQLite database. The database was created conveniently with the Room component. The App’s code is structured by features and all project architecture is MVVM — a recommended pattern by Android team.

    最后,我们的应用将用户数据作为SQLite数据库存储在设备内部。 该数据库是使用Room组件方便地创建的。 该应用程序的代码由功能构成,所有项目架构均为MVVM-Android团队推荐的模式。

资料库 (Repository)

Now, as you’re seeing the “Kriptofolio” (previously “My Crypto Coins”) app is really starting to take shape. With the latest repository commit for this part 3, you can find it nicely showing prepopulated database data for the user with the total holdings portfolio value calculated correctly.

现在,正如您所看到的那样,“ Kriptofolio”(以前称为“我的加密货币”)应用程序确实已经初具规模。 使用第3部分的最新存储库提交,您可以发现它很好地显示了用户的预填充数据库数据,并正确计算了总持股量值。

在GitHub上查看源代码 (View Source On GitHub)


Ačiū! Thanks for reading! I originally published this post for my personal blog www.baruckis.com on August 22, 2018.

阿奇! 谢谢阅读! 我最初于2018年8月22日在我的个人博客www.baruckis.com上发布了这篇文章。

翻译自: https://www.freecodecamp.org/news/kriptofolio-app-series-part-3/

桌面应用程序 架构

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值