The purpose of this repository is to showcase, with a very simple (but hopefully clear) sample Android project, how we implement Uncle Bob's Clean Architecture in our applications.This is not a working demo app: the only purpose of the classes in the project is to demonstrate how the dependency graphs work with the configuration explained below, and to illustrate which dependencies are typically involved in this type of architecture.
Given that broad nature of the topic and the amount of implementation details necessary to implement a working production project, we have simplified our example as much as possible and focused solely on the following areas:
api
/implementation
to hide unwanted dependenciesThere is no such thing as "the best architecture" when it comes to mobile applications: the best architecture approach for a project (or team) always depends on a series of factors and assumptions.
Our solution is based on specific requirements, and, although it might not be the silver bullet for every project, it works well and could help you define your own architecture or, at least, inspire you to think about it a bit more.
We came up with our solution (and we iteratively try to improve it) based on the following items:
Listed below, a quick description of each module and a class diagram with their relationships.
The following diagram illustrates the above mentioned modules relationships in this sample project.In order to support feature modules and (if properly configured) Instant Apps, the project's view/presentation layer is split into three modules; this is not a requirement and it can be avoided for small projects.
Module | Description | Module dependencies (direct or indirect) |
---|---|---|
entity | Business entities (the Entity layer in Clean) |
No dependencies |
data-bridge | "Bridge" module only used for the initialization of the Data layer. Prevents implementation details in the data layer from being accessible in the app module. |
data , data-access , entity |
data-access | The Data Access layer, interfaces for the business layer to access the data layer |
entity |
data | The Data layer, which includes networking, caching and data delivery for the business layer to manipulate. Exposes via Dagger the DataRepo dependencies to the business layer |
data-access , entity |
business | Business layer, contains interactors and business logic (which can then exposed to the presentation layer if necessary). | data-access , entity |
app-core | Core, base module for the view and presentation layer. Contains themes, styles, resources, strings and components that are used across apps and feature modules. | business , entity |
app-feature1 | View and presentation module for a "big" feature. This can be then extracted to use with Instant Apps if desired | app-core , business , entity |
app | View and presentation layers for the application module | app-core , app-feature1 , business , entity , data-bridge |
Google has done a very good job at producing a set of code examples in their Android Architecture Blueprints repository.We took inspiration from it (especially from the todo-mvp-clean, todo-mvp-dagger and dagger-android branches), but found that the examples are quite simple and not suited for more complex applications.More specifically:
Our Gradle modules use Dagger (and its Android extension) for dependency injection. As an architectural choice to ensure encapsulation and enforce layer boundaries,the modules at lower layers do not have access at compile time to the higher layers except its closest dependency (see graph - i.e., the presentation layer can only access the business layer, not the data(-access) layer).
Any exception to this rule must be explicitly declared and made available through a provision method in a public component.Dagger doesn't work well with this kind of requirement out of the box when using Subcomponents, since it needs to have access at compile time to all of the implementation classes to build the dependency graph (which is what we want to avoid in the first place).
The sample project doesn't cover other useful Dagger features such scopes and "feature" components; however, both can be easily plugged into our core project structure.
The following diagram illustrates the dependencies between components in our sample project.Notice how all dependency/inheritance arrows point to the business
layer. The entity
layer does not need a component as it mainly comprises pure entity objects and business logic.
In order to allow using Dagger with our encapsulation constraints, we ensure that:
Each Dagger Component
is internal
, and it is created and initialized within the module itself, so that each dependency graph is only fully visible inside the module. This guarantees encapsulation and allows us to declare both classes and the bound interfaces as internal
if we don't want to provide access to them outside of the module.Modules and dependencies are, by default, only accessible by components in the same layer.
This interface only includes the dependencies that we want to expose outside of the module, e.g.:
interface BusinessComponent {
// provision methods for dependencies exposed to the presentation layer
}
@Component
internal interface InternalBusinessComponent : BusinessComponent
interface DataAccessComponent { // in the `data-access` module
// provision methods for data layer dependencies exposed to the business layer
}
@Component // in the `data` module
internal interface DataComponent : DataAccessComponent
By doing so, we also encapsulate the usage of Dagger within the module itself, without forcing external "client code" to use the framework, and simplifying injecting a mock of the whole component for testing when needed.
Each layer which has a direct dependency to a component from another layer, will declare so in its Dagger component as a component dependency:
@Component(modules = [...], dependencies = [DataAccessComponent::class])
internal interface InternalBusinessComponent : BusinessComponent
Dagger has recently introduced component factories, which allow (sub)components to provide an interface, annotated with @Component.Factory
(or @Subcomponent.Factory
). The interface provides a single function, which contains dependencies (modules, components or any other) that the Component
requires at dependency graph creation.We use component factories to pass the components which are dependencies in the layer we are initialising, along with other classes that might be passed on from lower level layers (e.g. the application Context
) with @BindsInstance.
@Component(..., dependencies = [DataAccessComponent::class])
internal interface InternalBusinessComponent : BusinessComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance applicationContext: Context,
dataAccessComponent: DataAccessComponent
): InternalBusinessComponent
}
}
Note: initialization code is ugly! The sample provides the simplest way to kick off the dependency graphs for each component and trigger initialization of dependencies that require it at application startup. Each project could require a different approach, the only requirement here is to follow the same layer initialisation order shown below.
The trigger for the initialization process is, as usual, the Application.onCreate()
method.In order to provide layer-specific initialization on each module, the sample provides a SampleBusinessApplication
abstract class in the business layer, and a SampleApplication
class, usually in the application module.These classes provide callbacks to initialize the layers' components (in this order):
initializeDataComponent()
val businessComponent: BusinessComponent = initializeBusinessComponent()
initializeAppComponent(businessComponent) // the presentation/view layers need the business layer to be initialized
data-bridge
moduleIn order to fulfill the desired level of encapsulation dictated by Clean Architecture, the data
layer is not directly accessible from other layers (and modules), and it is used by the business layer through the data-access
layer.The data-bridge
only purpose is to temporarily "break" the dependency inversion rule at initialization time to provide a DataBridgeInitializer
; this is accessed by the application module to call to the data
layer and trigger the Dagger dependency graph initialization for DataComponent
.
data
layer through the data-bridge
module: DataBridgeInitializer
calls to DataLayerInitializer
, which executes the component factory's create()
method for DataComponent
and sets the singleton instance into DataComponent.INSTANCE
and DataAccessComponent.INSTANCE
(for access from the business
layer)business
layer: BusinessLayerInitializer
, called by SampleBusinessApplication
, which executes the component factory's create()
method for BusinessInternalComponent
and sets the singleton instance into BusinessInternalComponent.INSTANCE
(DataAccessComponent.INSTANCE
is passed to create()
)presentation/view
layer: initializeAppComponent(businessComponent)
is called, and the ApplicationComponent.create()
factory method is executedOnce all the Dagger dependency graphs are created, the application can then move on to the rest of its initialization process.Note: this section is intentionally verbose and requires you to go through the code while reading. You can probably skip it if you are already familiar with Dagger.
We have three separate public Dagger Component
s in our codebase: ApplicationComponent
(view/presentation layer), BusinessComponent
and DataAccessComponent
.These are declared in the corresponding layer's module to make sure that the Dagger annotation processor and compiler have access to all the required dependencies from the generated provider classes.
Let's take our Feature2DetailsPresenter
example and follow its dependencies from the bottom-up in the architecture hierarchy:
Feature2DetailsActivity
is created, an injector method is called in the onCreate()
Feature2DetailsPresenter
must be created: the class has an @Inject
constructor that Dagger uses to instantiate itFeature2DetailsInteractor
is required by the constructor: we need to access the class provider, which is declared in BusinessComponent
GLOBAL_COMPUTATION_EXECUTOR
is also injected in the constructor. Note that this is provided by BusinessComponent
but exposed all the way from DataAccessComponent
(this kind of transitive dependency is sometimes useful)BusinessComponent
exposes Feature2DetailsInteractor
via a provision method (feature2DetailsInteractor()
)InteractorsBindingModule
(Feature2DetailsInteractor
binds to Feature2DetailsInteractorImpl
)Feature2DetailsInteractorImpl
has dependencies from the data access layer: Entity1Repo
is one of thoseFeature2DetailsInteractorImpl
also requires InternalInteractor
, which is bound in InteractorsBindingModule
, but not exposed in BusinessComponent
(but available in InternalBusinessComponent
)DataComponent
extends from the DataAccessComponent
: all the provision methods for data access layer classes which are needed in the business layer are available hereDataAccessComponent
exposes the needed provision method: entity1Repo(): Entity1Repo
SampleDataComponent
includes DataRepoBindingModule
, which, finally, contains the binding method which provides an instance of Entity1RepoImpl
for the Entity1Repo
interfaceCopyright 2018-2020 Teamwork.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Domain Layer拥有所有的业务逻辑。Domain层从命名为User Case或者interactor的类开始,这些类由展示层(Presenter)来使用。这些User Case代表开发人员可以从展示层(Presenter)执行的所有可能的操作。 上一篇文章描述了todo-mvp,并着重描述了一下Data层。这次在此基础上又增加了domain 层。 按照Android-CleanArchit
项目地址:https://github.com/googlesamples/android-architecture architecture的解释:n. 建筑学;建筑风格;建筑式样;建筑艺术;架构。 说明该项目就是GG官方的一个关于架构的示例 项目down下来之后,使用git 查看分支就是一个个简单的架构示例 如下: master todo-databinding * todo-mvp
This repository holds 2 projects: Sample Giphy App is a test Project that displays paginated trending GIFs from Giphy and also contains search functionality. This small project is a good starting poin
Android Clean Architecture Boilerplate 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 boile
我刚刚读了应用程序架构指南。在这篇文章中,google演示了使用新的android架构组件构建健壮的android应用程序的基本指南。如今年的Google I/O所示,一个应用程序应该有4层: 在这种情况下,依赖关系从上到下流动,较高层只知道直接位于它们下面的组件,例如。UI控制器只知道ViewModel,ViewModel只知道存储库等。 因此,我观看了视频,完成了相应的codelabs,然后学
首先,我知道这是StackOverflow之前出现的一个已知的bug,但提供的解决方案对我来说似乎是暂时的,或者只在某些时候起作用。 请参见:无法解析符号“AppCompattivity”并且无法解析AppCompattivity... 这些线程建议的修复为我提供了临时的修复,这些修复似乎在我清理项目的那一刻就被撤消了,然后再推到Github(用于Udacity Nanodegree程序)。 **
我有一些第三方jar依赖。因此,我使用maven-install-plugin将这些第三方jar安装到我的本地存储库(.m2/repository)中。这个插件一定要清理阶段。当我执行“MVN clean Install”时,在运行clean之前,它开始搜索依赖项,最终构建失败,因为它无法找到第三方JAR。但是当我单独运行mvn clean时,它会将文件安装在本地存储库中。随后当我运行mvn cl
Getting Started If you don't have Docker, download it from the address below. https://www.docker.com/products/docker-desktop Download this repository Open onion-architecture.sln with Visual studio Con