Android Clean Architecture MVI Boilerplate
Note: This is a fork of our original Clean Architecture Boilerplate, except in this repo we have switched out the MVP approach found in the presentation layer to now use ViewModels from the Android Architecture Components Library.
The caching layer now also uses Room.
注意:这是我们最初的Clean Architecture Boilerplate的一个分支,在这个 repo 中,我们已经将表示层中的 MVP 方法切换为现在使用 Android 架构组件库中的 ViewModel。缓存层现在也使用 Room。
Welcome 👋 We hope this boilerplate is not only helpful to other developers, but also that it helps to educate in the area of architecture. We created this boilerplate for a few reasons:
欢迎👋我们希望这个样板不仅对其他开发人员有所帮助,而且有助于在架构领域进行教育。我们创建这个样板有几个原因:
- To experiment with modularisation
尝试模块化
- To experiment with modularisation
- To experiment with the Android Architecture Components
尝试 Android 架构组件
- To experiment with the Android Architecture Components
- To share some approaches to clean architecture, especially as we've been talking a lot about it
分享一些纯净的架构方法,尤其是在我们谈论它的时候
- To share some approaches to clean architecture, especially as we've been talking a lot about it
- To use as a starting point in future projects where clean architecture feels appropriate
在未来的项目中使用纯净的架构是一个合适的起点
- To use as a starting point in future projects where clean architecture feels appropriate
It is written 100% in Kotlin with both UI and Unit tests - we will also be keeping this up-to-date as libraries change!
它是 100% 用 Kotlin 编写的,带有 UI 和单元测试——随着库的变化,我们也会保持最新!
Disclaimer 免责声明
Note: The use of clean architecture may seem over-complicated for this sample project. However, this allows us to keep the amount of boilerplate code to a minimum and also demonstrate the approach in a simpler form.
注意:对于这个示例项目,使用纯净的架构可能看起来过于复杂。但是,这使我们能够将样板代码的数量保持在最低限度,并以更简单的形式演示该方法。
Clean Architecture will not be appropriate for every project, so it is down to you to decide whether or not it fits your needs 🙂
纯净架构并不适合每个项目,因此由您决定它是否适合您的需求🙂
Languages, libraries and tools used 使用的语言、库和工具
- Kotlin
- Room
- Android Architecture Components
- Android Support Libraries
- RxJava2
- Dagger 2 (2.11)
- Glide
- Retrofit
- OkHttp
- Gson
- Timber 是一个轻量级的第三方库,能够帮助开发者更好的使用Android Log
- Mockito Java 开发的模拟测试框架
- Espresso Android UI 的快速自动化测试框架
- Robolectric Android单元测试框架
Requirements 要求
- JDK 1.8
- Android SDK
- Android O (API 26)
- Latest Android SDK Tools and build tools.
Architecture 架构
The architecture of the project follows the principles of Clean Architecture. Here's how the sample project implements it:
运行时的示例应用程序将向您显示所有 Bufferoos(Buffer 团队成员!)的简单列表。
The sample app when run will show you a simple list of all the Bufferoos (Buffer team members!).
这个项目运行起来将会展示所有的项目贡献者的简介列表
Let's look at each of the architecture layers and the role each one plays :)
让我们看看每一个架构层以及每一层所扮演的角色:)
User Interface(用户页面)
This layer makes use of the Android Framework and is used to create all of our UI components to display inside of the Browse Activity. The layer receives its data from the Presentation layer and when retrieved, the received models are mapped using the Bufferoo Mapper so that the model can be mapped to this layer's interpretation of the Bufferoo instance, which is the BufferooViewModel. The Activity makes use of the BrowseBufferoosViewModel to retrieve data.
该层使用 Android 框架,用于创建我们所有的 UI 组件以显示在Browse Activity中。该层从表示层接收其数据,并在检索到时,使用Bufferoo Mapper映射接收到的模型,以便可以将模型映射到该层对 Bufferoo 实例的解释,即BufferooViewModel。Activity 使用BrowseBufferoosViewModel来检索数据。
Presentation(表示层)
This layer's responsibility is to handle the presentation of the User Interface, but at the same time knows nothing about the user interface itself. This layer has no dependence on the Android Framework, it is a pure Kotlin module. Each ViewModel class that is created implements the ViewModel class found within the Architecture components library. This ViewModel can then be used by the UI layer to communicate with UseCases and retrieve data. The BrowseBufferoosViewModel returns an instance of a BrowseUiModel which contains data that can be used by the UI.
这一层的职责是处理用户界面的呈现,但同时对用户界面本身一无所知。该层不依赖 Android Framework,是一个纯 Kotlin 模块。创建的每个 ViewModel 类都实现了在架构组件库中找到的 ViewModel 类。然后,UI 层可以使用此 ViewModel 与 UseCases 通信并检索数据。BrowseBufferoosViewModel返回一个 BrowseUiModel 的实例,其中包含UI 可以使用的数据。
The ViewModels use an instance of a FlowableUseCase from the Domain layer to retrieve data. Note here that there is no direct name reference to the UseCase that we are using - we do inject an instance of the GetBufferoos UseCase, however.
ViewModel 使用来自Domain层的FlowableUseCase实例来检索数据。请注意,我们正在使用的 UseCase 没有直接的名称引用 - 但是,我们确实注入了GetBufferoos UseCase 的实例。
The ViewModel receives data from the Domain layer in the form of a Bufferoo. These instances are mapped to instance of this layers model, which is a BufferooView using the BufferooMapper.
ViewModel 以Bufferoo的形式从 Domain 层接收数据。这些实例被映射到这个层模型的实例,它是一个使用 BufferooMapper 的BufferooView。
Domain(域)
The domain layer responsibility is to simply contain the UseCase instance used to retrieve data from the Data layer and pass it onto the Presentation layer. In our case, we define a GetBufferoos - this use case handles the subscribing and observing of our request for data from the BufferooRepository interface. This UseCase extends the FlowableUseCase base class - therefore we can reference it from outer layers and avoid a direct reference to a specific implementation.
领域层的职责是简单地包含用于从数据层检索数据并将其传递到表示层的 UseCase 实例。在我们的例子中,我们定义了一个GetBufferoos——这个用例处理我们对来自 BufferooRepository 接口的数据请求的订阅和观察。这个 UseCase 扩展了FlowableUseCase基类——因此我们可以从外层引用它,避免直接引用特定的实现。
The layer defines the Bufferoo class but no mapper. This is because the Domain layer is our central layer, it knows nothing of the layers outside of it so has no need to map data to any other type of model.
该层定义了Bufferoo类但没有映射器。这是因为 Domain 层是我们的中心层,它对它之外的层一无所知,因此不需要将数据映射到任何其他类型的模型。
The Domain layer defines the BufferooRepository interface which provides a set of methods for an external layer to implement as the UseCase classes use the interface when requesting data.
Domain 层定义了BufferooRepository接口,它为外部层提供了一组方法来实现,因为 UseCase 类在请求数据时使用该接口。
Data(数据层)
The Data layer is our access point to external data layers and is used to fetch data from multiple sources (the cache and network in our case). It contains an implementation of the BufferooRepository, which is the BufferooDataRepository. To begin with, this class uses the BufferooDataStoreFactory to decide which data store class will be used when fetching data - this will be either the BufferooRemoteDataStore or the BufferooCacheDataStore - both of these classes implement the BufferooDataStore repository so that our DataStore classes are enforced.
数据层是我们对外部数据层的访问点,用于从多个来源(在我们的案例中为缓存和网络)获取数据。它包含 BufferooRepository 的实现,即BufferooDataRepository。首先,此类使用BufferooDataStoreFactory来决定在获取数据时将使用哪个数据存储类 - 这将是BufferooRemoteDataStore或BufferooCacheDataStore - 这两个类都实现了BufferooDataStore存储库,以便强制执行我们的 DataStore 类。
Each of these DataStore classes also references a corresponding BufferooCache and BufferooRemote interface, which is used when requesting data from an external data source module.
这些 DataStore 类中的每一个还引用了相应的BufferooCache和BufferooRemote接口,用于从外部数据源模块请求数据。
This layers data model is the BufferooEntity. Here the BufferooMapper is used to map data to and from a Bufferoo instance from the domain layer and BufferooEntity instance from this layer as required.
该层数据模型是BufferooEntity。这里BufferooMapper用于根据需要将数据映射到域层的 Bufferoo 实例和该层的 BufferooEntity 实例。
Remote(服务器层)
The Remote layer handles all communications with remote sources, in our case it makes a simple API call using a Retrofit interface. The BufferooRemoteImpl class implements the BufferooRemote interface from the Data layer and uses the BufferooService to retrieve data from the API.
Remote 层处理与远程源的所有通信,在我们的例子中,它使用 Retrofit 接口进行简单的 API 调用。BufferooRemoteImpl类从 Data 层实现BufferooRemote接口,并使用BufferooService从 API 中检索数据。
The API returns us instances of a BufferooModel and these are mapped to BufferooEntity instance from the Data layer using the BufferooEntityMapper class.
API 向我们返回BufferooModel 的实例,这些实例使用 BufferooEntityMapper类从数据层映射到 BufferooEntity 实例。
Cache(缓存)
The Cache layer handles all communication with the local database which is used to cache data.
缓存层处理与用于缓存数据的本地数据库的所有通信
The data model for this layer is the CachedBufferoo and this is mapped to and from a BufferooEntity instance from the Data layer using the BufferooEntityMapper class.
该层的数据模型是CachedBufferoo ,它使用BufferooEntityMapper类映射到数据层的 BufferooEntity 实例和从该实例映射。
Conclusion
We will be happy to answer any questions that you may have on this approach, and if you want to lend a hand with the boilerplate then please feel free to submit an issue and/or pull request.
我们很乐意回答您对这种方法可能有的任何问题,如果您想帮助样板,请随时提交问题和/或拉取请求🙂
Again to note, use Clean Architecture where appropriate. This is example can appear as over-architectured for what it is - but it is an example only. The same can be said for individual models for each layer, this decision is down to you. In this example, the data used for every model is exactly the same, so some may argue that "hey, maybe we don't need to map between the presentation and user-interface layer". Or maybe you don't want to modularise your data layer into data/remote/cache and want to just have it in a single 'data' module. That decision is down to you and the project that you are working on 🙌🏻
再次注意,在适当的地方使用清洁架构。这个例子可能看起来像是过度架构的——但这只是一个例子。对于每一层的单个模型也可以这样说,这个决定取决于你。在这个例子中,每个模型使用的数据都是完全相同的,所以有些人可能会争辩说“嘿,也许我们不需要在表示层和用户界面层之间进行映射”。或者,也许您不想将数据层模块化为数据/远程/缓存,而只想将其放在单个“数据”模块中。这个决定取决于你和你正在进行的项目🙌🏻
Thanks(感谢)
A special thanks to the authors involved with these two repositories, they were a great resource during our learning!
特别感谢参与这两个存储库的作者,他们是我们学习过程中的重要资源!