diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 662b7cb..32495ef 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,8 @@ + + @@ -119,6 +122,7 @@ + diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..40d9a4b --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 92084a2..96c104f 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,10 +1,13 @@ + diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..0380d8d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 0a20f92..2d5436c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,9 @@ - + + @@ -10,27 +11,41 @@ - + diff --git a/.idea/modules.xml b/.idea/modules.xml index 40ccb54..b880f92 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,15 +2,38 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 9bb4150..8f9b5b6 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,98 @@ -[![Build Status](https://app.bitrise.io/app/f4515a5c1a063849/status.svg?token=-SdbHgFYqsRzNe9OhOwk3g&branch=master)](https://app.bitrise.io/app/f4515a5c1a063849) - -# Clean Architecture on Android: the Teamwork.com way! -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](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) 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: +# Clean Architecture on Android: a pragmatic way +The purpose of this repository is to showcase, with a very simple (but hopefully clear) sample Android +project, how I get inspiration from Uncle Bob's [Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) +to structure the application I work on. +This is a more updated fork of the [sample project](https://github.com/Teamwork/android-clean-architecture) +I showcased while working for Teamwork a good few years ago. + +**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. The fact that the project compiles, as simple as it sounds, +is the demo you need! + +Given that broad nature of the topic and the amount of implementation details necessary to implement +a working production project, I have simplified the example as much as possible and focused solely on +the following areas: - **Module structure:** each architecture layer has its own module, following closely the _Clean_ principles and naming. - **Separation of layers**: how to configure Gradle making use of `api`/`implementation` to hide unwanted dependencies - **Dependency Injection:** how to set up *Dagger 2* for a multi-module environment, but still ensuring the above points -## Our requirements -There 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: -- **Software is our craft.** We aim for our applications to be fast, as bug-free as possible and always suiting our customers' needs: the only way to achieve that is to **ensure the quality and maintainability of our code through the use of best practices**. -- **We believe in code reusability.** Modularising components is the only way to ensure that our code is reusable across products, maximise our bandwidth as a team and ensure that bug fixes are promptly delivered to all of our clients. -- **Our applications are big.** Most of our applications are complex, with non-trivial logic and a significant amount of screens and use cases: **structuring our code in a formal and clear way is essential**. -- **Our applications should endure time.** We don't like technical debt, and we don't like rewriting the same software, using the same technologies, only because that code is broken. -- **We need to scale fast and make new developers onboarding smooth.** Using a shared, well-defined architecture helps new developers in the team, who should then be able to get into the codebase faster and contribute to it from the get-go. +## Requirements +There 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. + +This specific requirements, and, although it might not be the _silver bullet_ for every scenario, it +works well on a broad variety of project sizes and types, and could help you define your own architecture +or, at least, inspire you to think about it a bit more. + +This solution (always subject to refinement and improvements) is based on the following principles: +- **Software craftsmanship.** Aim for applications to be fast, as bug-free as possible and always suiting +your users' needs: the only way to achieve that is to **ensure the quality and maintainability of code +through the use of best practices**. +- **Software changes often.** Any architecture must support and facilitate change; providing a solid and +well crafted project structure helps keeping a codebase tidy, consistent and testable, separating concerns +and layers so that a change in one of them doesn't impact all others. +- **Code reusability.** Modularising components is the only way to ensure that code is reusable across +projects, maximising bandwidth as a team and ensure that bug fixes and improvements are promptly delivered. +- **Almost no application is trivial.** Most applications contain non-trivial logic and/or an amount of +screens and use cases that justifies all of the above: **structuring code in a formal and clear way is essential**. +- **Applications should endure time.** nobody likes technical debt, and we should aim to never have to rewrite +the same software, using the same technologies, only because that code is broken. +- **Quick team scaling and developers onboarding.** Using a shared, well-defined architecture helps new +developers in a team; using well known practices and conventions helps them get into a codebase faster. ## Modules Listed below, a quick description of each module and a class diagram with their relationships. ### Modules 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. +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. ![](docs/clean_app_architecture_v2.1.png) ### Modules description -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` +| 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 Android Architecture Samples -Google has done a very good job at producing a set of code examples in their [Android Architecture Blueprints](https://github.com/android/architecture-samples) repository. -We took inspiration from it (especially from the [todo-mvp-clean](https://github.com/android/architecture-samples/tree/todo-mvp-clean), [todo-mvp-dagger](https://github.com/android/architecture-samples/tree/todo-mvp-dagger) and [dagger-android](https://github.com/android/architecture-samples/tree/dagger-android) branches), but found that the examples are quite simple and not suited for more complex applications. +Google has done a very good job at producing a set of code examples in their [Android Architecture Blueprints](https://github.com/android/architecture-samples) +repository. I took inspiration from some of the patterns implemented there, but found that the examples are just too +simple and not suited for more complex applications. More specifically: - It is well suited for small projects, but the _"monolith module"_ approach doesn't scale well for medium/large applications - The package-based separation of layers cannot be enforced at compile-time and is therefore very error-prone (especially when working in a big team) -- It is only a partial implementation of *Clean*: there is no real separation between presentation and business layer (*presenters* and *use cases*) -- It does not allow sharing code across applications, nor it is suitable for feature modules or _Instant Apps_ +- It is only a partial implementation of *Clean*: there is no real separation between presentation, data and business layer +(*view-model/presenter*, *repositories* and *use cases/entities*) +- It does not allow sharing code across applications ## Dependency Injection -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_). +Our *Gradle* modules use _the **Dagger**_ framework (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_ - e.g., +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). +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 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. ### Components relationships -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. +The following diagram illustrates the dependencies between components in the 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. ![](docs/clean_app_architecture_components_v2.1.png) @@ -71,7 +100,10 @@ Notice how all dependency/inheritance arrows point to the `business` layer. The In order to allow using _Dagger_ with our encapsulation constraints, we ensure that: #### Each layer owns its Dagger component -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. +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_. #### Each layer's Dagger component inherits a public plain interface @@ -94,18 +126,25 @@ interface DataAccessComponent { // in the `data-access` 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. +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. #### Dependencies between layers are fully managed by Dagger -Each layer which has a direct dependency to a component from another layer, will declare so in its Dagger component as a [component dependency](https://dagger.dev/api/2.25.2/dagger/Component.html#dependencies): +Each layer which has a direct dependency to a component from another layer, will declare so in its +Dagger component as a [component dependency](https://dagger.dev/api/2.25.2/dagger/Component.html#dependencies): ``` @Component(modules = [...], dependencies = [DataAccessComponent::class]) internal interface InternalBusinessComponent : BusinessComponent ``` #### Component Factory -Dagger has recently introduced [component factories](https://dagger.dev/api/2.25.2/dagger/Component.Factory.html), 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](https://dagger.dev/api/2.25.2/dagger/BindsInstance.html). +Dagger has introduced [component factories](https://dagger.dev/api/2.25.2/dagger/Component.Factory.html), +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](https://dagger.dev/api/2.25.2/dagger/BindsInstance.html). ``` @Component(..., dependencies = [DataAccessComponent::class]) internal interface InternalBusinessComponent : BusinessComponent { @@ -120,10 +159,14 @@ internal interface InternalBusinessComponent : BusinessComponent { ``` #### Initialization -**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. +**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. +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() @@ -132,22 +175,36 @@ initializeAppComponent(businessComponent) // the presentation/view layers need t ``` ##### The `data-bridge` module -In 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`. +In 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`. ##### Initialization steps -- **`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 executed +- **`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 executed + Once all the Dagger dependency graphs are created, the application can then move on to the rest of its initialization process. ### Dependency Injection: example -**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_. +**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. +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: +Let's take our `Feature2DetailsPresenter` example and follow its dependencies from the bottom-up in the +architecture hierarchy: #### Presentation layer - When the default activity `Feature2DetailsActivity` is created, an injector method is called in the `onCreate()` @@ -172,7 +229,8 @@ Let's take our `Feature2DetailsPresenter` example and follow its dependencies fr ## License - Copyright 2018-2020 Teamwork.com + Copyright 2022 - Marco Salis + Copyright 2018 - Teamwork.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/build.gradle b/build.gradle index b207312..545a409 100644 --- a/build.gradle +++ b/build.gradle @@ -1,34 +1,21 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext.kotlin_version = '1.3.61' - - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } +plugins { + id 'com.android.application' version '7.4.1' apply false + id 'com.android.library' version '7.4.1' apply false + id 'org.jetbrains.kotlin.android' version '1.8.0' apply false } allprojects { - repositories { - mavenCentral() - google() - jcenter() - } project.ext { - sharedCompileSdkVersion = 29 - sharedMinSdkVersion = 21 - sharedTargetSdkVersion = 29 - sharedBuildToolsVersion = "29.0.2" + sharedCompileSdkVersion = 33 + sharedMinSdkVersion = 24 + sharedTargetSdkVersion = 33 + sharedBuildToolsVersion = "33.0.0" - appVersionCode = 3 - appVersionName = "0.3" + appVersionCode = 5 + appVersionName = "0.5" } } @@ -36,51 +23,30 @@ ext { // dependency versions (sorted alphabetically) versions = [ // production dependencies - butterknife : '10.2.0', - dagger : '2.25.2', + dagger : '2.45', jsrAnnotations : '3.0.2', - moshi : '1.8.0', - okhttp : '3.11.0', - retrofit : '2.6.0', - room : '1.1.1', - rxandroid : '2.1.0', - rxjava : '2.2.2', - timber : '4.7.1', + moshi : '1.9.2', + okhttp : '4.10.0', + timber : '5.0.1', // AndroidX - https://developer.android.com/jetpack/androidx/versions androidx : [ - annotation : '1.1.0', - appCompat : '1.1.0', - archCoreRuntime : '2.0.1', - cardview : '1.0.0', - constraintLayout : '1.1.3', + annotation : '1.5.0', + appCompat : '1.6.1', + constraintLayout : '2.1.4', core : '1.1.0', coreUtils : '1.0.0', - espressoCore : '3.1.0', fragment : '1.0.0', - legacyPreferenceV14 : '1.0.0', - lifecycleCompiler : '2.0.0', - lifecycleExtensions : '2.0.0', - lifecycleRuntime : '2.0.0', - material : '1.0.0', - media : '1.0.1', - palette : '1.0.0', - supportCoreUi : '1.0.0', - supportV4 : '1.0.0', - recyclerview : '1.0.0', - room : '2.2.0', - vectordrawableAnimated: '1.1.0', + material : '1.8.0', // AndroidX test dependencies - roomTesting : '2.2.0', - test : '1.2.0', - testExt : '1.1.1' + test : '1.5.0', + testExt : '1.1.5' ], // Test dependencies espressoCore : '3.0.2', - junit : '4.12', - mockito : '2.15.0', + junit : '4.13.2', supportTest : '1.0.2' ] } diff --git a/gradle.properties b/gradle.properties index 8de5058..7f69adb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,6 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 85d27cd..8619013 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 21 17:37:29 IST 2019 +#Fri Feb 17 16:47:59 CET 2023 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/sample-app-core/build.gradle b/sample-app-core/build.gradle index e3ae522..6a31321 100644 --- a/sample-app-core/build.gradle +++ b/sample-app-core/build.gradle @@ -1,11 +1,11 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion kotlinOptions { jvmTarget = '1.8' @@ -23,7 +23,6 @@ android { buildTypes { debug { minifyEnabled false - multiDexEnabled true } release { minifyEnabled false @@ -38,8 +37,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies api project(':sample-business') @@ -60,7 +57,5 @@ dependencies { androidTestImplementation "androidx.test:runner:${versions.androidx.test}" androidTestImplementation "androidx.test:rules:${versions.androidx.test}" androidTestImplementation "androidx.test.ext:junit:${versions.androidx.testExt}" - androidTestImplementation "androidx.test.espresso:espresso-core:${versions.androidx.espressoCore}" androidTestImplementation "junit:junit:${versions.junit}" - androidTestImplementation "org.mockito:mockito-android:${versions.mockito}" } \ No newline at end of file diff --git a/sample-app-feature1/build.gradle b/sample-app-feature1/build.gradle index 065a401..dbad970 100644 --- a/sample-app-feature1/build.gradle +++ b/sample-app-feature1/build.gradle @@ -1,11 +1,10 @@ -// apply plugin: 'com.android.feature' // use this instead of 'com.android.library' for Instant App -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion kotlinOptions { jvmTarget = '1.8' @@ -23,7 +22,6 @@ android { buildTypes { debug { minifyEnabled false - multiDexEnabled true } release { @@ -39,8 +37,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies implementation project(':sample-app-core') @@ -55,8 +51,6 @@ dependencies { androidTestImplementation "androidx.test:runner:${versions.androidx.test}" androidTestImplementation "androidx.test:rules:${versions.androidx.test}" androidTestImplementation "androidx.test.ext:junit:${versions.androidx.testExt}" - androidTestImplementation "androidx.test.espresso:espresso-core:${versions.androidx.espressoCore}" androidTestImplementation "junit:junit:${versions.junit}" - androidTestImplementation "org.mockito:mockito-android:${versions.mockito}" } diff --git a/sample-app/build.gradle b/sample-app/build.gradle index a0981cd..7ec6789 100644 --- a/sample-app/build.gradle +++ b/sample-app/build.gradle @@ -1,11 +1,15 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion + + buildFeatures { + viewBinding = true + } kotlinOptions { jvmTarget = '1.8' @@ -24,7 +28,6 @@ android { buildTypes { debug { - multiDexEnabled true } release { minifyEnabled false @@ -39,8 +42,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies @@ -66,8 +67,6 @@ dependencies { androidTestImplementation "androidx.test:runner:${versions.androidx.test}" androidTestImplementation "androidx.test:rules:${versions.androidx.test}" androidTestImplementation "androidx.test.ext:junit:${versions.androidx.testExt}" - androidTestImplementation "androidx.test.espresso:espresso-core:${versions.androidx.espressoCore}" androidTestImplementation "junit:junit:${versions.junit}" - androidTestImplementation "org.mockito:mockito-android:${versions.mockito}" } diff --git a/sample-app/src/main/AndroidManifest.xml b/sample-app/src/main/AndroidManifest.xml index c68b969..5457c27 100644 --- a/sample-app/src/main/AndroidManifest.xml +++ b/sample-app/src/main/AndroidManifest.xml @@ -4,7 +4,6 @@ package="com.teamwork.android.samples.clean.app"> + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> diff --git a/sample-app/src/main/java/com/teamwork/android/samples/clean/app/SampleActivity.kt b/sample-app/src/main/java/com/teamwork/android/samples/clean/app/SampleActivity.kt index 25b1b34..03854c9 100644 --- a/sample-app/src/main/java/com/teamwork/android/samples/clean/app/SampleActivity.kt +++ b/sample-app/src/main/java/com/teamwork/android/samples/clean/app/SampleActivity.kt @@ -4,11 +4,11 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity +import com.teamwork.android.samples.clean.app.databinding.ActivitySampleBinding import com.teamwork.android.samples.clean.feature1.detail.Feature1DetailsPresenter import com.teamwork.android.samples.clean.feature1.detail.Feature1DetailsView import com.teamwork.android.samples.clean.feature1.list.Feature1ListPresenter import com.teamwork.android.samples.clean.feature1.list.Feature1ListView -import kotlinx.android.synthetic.main.activity_sample.* import javax.inject.Inject class SampleActivity : AppCompatActivity(), Feature1ListView, Feature1DetailsView { @@ -24,8 +24,12 @@ class SampleActivity : AppCompatActivity(), Feature1ListView, Feature1DetailsVie SampleApplication.appComponent.inject(this) + val binding = ActivitySampleBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + setContentView(R.layout.activity_sample) - setSupportActionBar(toolbar) + setSupportActionBar(binding.toolbar) presenter.onViewCreated(this) detailsPresenter.onViewCreated(this) diff --git a/sample-business/build.gradle b/sample-business/build.gradle index 6a6b826..37dccda 100644 --- a/sample-business/build.gradle +++ b/sample-business/build.gradle @@ -1,10 +1,11 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion kotlinOptions { jvmTarget = '1.8' @@ -22,7 +23,6 @@ android { buildTypes { debug { minifyEnabled false - multiDexEnabled true } release { minifyEnabled false @@ -37,8 +37,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies implementation project(':sample-data-access') @@ -46,7 +44,6 @@ dependencies { // third party dependencies - api "androidx.appcompat:appcompat:${versions.androidx.appCompat}" api "androidx.annotation:annotation:${versions.androidx.annotation}" // Dagger for Dependency Injection diff --git a/sample-data-access/build.gradle b/sample-data-access/build.gradle index 34c72a3..d5b09aa 100644 --- a/sample-data-access/build.gradle +++ b/sample-data-access/build.gradle @@ -1,9 +1,10 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion kotlinOptions { jvmTarget = '1.8' @@ -21,7 +22,6 @@ android { buildTypes { debug { minifyEnabled false - multiDexEnabled true } release { minifyEnabled false @@ -36,8 +36,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies api project(':sample-entity') diff --git a/sample-data-bridge/build.gradle b/sample-data-bridge/build.gradle index d46ae85..2a22afc 100644 --- a/sample-data-bridge/build.gradle +++ b/sample-data-bridge/build.gradle @@ -1,9 +1,10 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion kotlinOptions { jvmTarget = '1.8' @@ -21,7 +22,6 @@ android { buildTypes { debug { minifyEnabled false - multiDexEnabled true } release { minifyEnabled false @@ -35,8 +35,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies diff --git a/sample-data/build.gradle b/sample-data/build.gradle index 91240a0..4930095 100644 --- a/sample-data/build.gradle +++ b/sample-data/build.gradle @@ -1,10 +1,11 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' +} android { compileSdkVersion project.ext.sharedCompileSdkVersion - buildToolsVersion project.ext.sharedBuildToolsVersion kotlinOptions { jvmTarget = '1.8' @@ -22,7 +23,6 @@ android { buildTypes { debug { minifyEnabled false - multiDexEnabled true } release { minifyEnabled false @@ -37,8 +37,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // internal dependencies implementation project(':sample-data-access') diff --git a/sample-entity/build.gradle b/sample-entity/build.gradle index 5dfe9e6..a04a14a 100644 --- a/sample-entity/build.gradle +++ b/sample-entity/build.gradle @@ -9,14 +9,11 @@ buildscript { mavenCentral() } dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0" } } dependencies { - //noinspection DifferentStdlibGradleVersion - false positive - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" - // third party dependencies diff --git a/settings.gradle b/settings.gradle index 0719eb8..f3b0d6a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + include ':sample-app', ':sample-app-core', ':sample-app-feature1', ':sample-business', ':sample-data-bridge', ':sample-data', ':sample-data-access', ':sample-entity'