Android — Clean architecture with Dynamic-features and Hilt/Dagger2 (Pt.1)
Introduction
Dynamic-feature is a well-known approach that Google developed to reduce the APK size by using bundles, it was released in 2018 and it is going to be used in this article to separate our project into feature modules, but there’s something “new” despite the dynamic-feature that we are going to talk about, if you are an android developer, you probably have worked with some kind of Dependency Injection or Service Locator library, like Koin, Kodein, and the most popular… Dagger.
But last year, Google came up with a new player, announcing a better and easier way to handle dependency injection… *drum roll*… Hilt.
Hilt is a dependency injection library based on Dagger that reduces the boilerplate of manually defining dependencies in your project. It is not that hard to set up in your project, and Google describes pretty well how to do so, even if your project is using dynamic features. So, I’m going to show and describe in this article how was my journey to implement an application using Hilt and dynamic features on top of Clean Architecture.
This will be the first of a series of articles and for now, we are going to tackle the clean architecture and Gradle setup to use dynamic-feature navigation and Hilt.
The subject…
The application I implemented was a Bitcoin market app that allows the user to access stats, charts, and pools related to updated Bitcoin data. To make the requests, the API used was the Blockchain Charts API. It is pretty simple and easy to use, and there’s no authorization required (Easy peasy).
Github
Preview
PS: The GIF quality is not the best, and the colors are looking a little bit different.
The structure…
The package structure is described on my Github page, and here I’m going to explain a little bit the relationship and structure of the modules:
Each tab on the bottom navigation view represents one feature, so charts, pools, and stats are all dynamic features defined on the app “build.gradle”. Each feature has its own “clean architecture” structure that applies the following approach:
- UI module implements the domain layer to have access to the necessary use cases and implements also the data layer to provide the dependency injection for the repositories (Otherwise we will not be able to provide the repository instances);
- The Data Layer depends only on the domain layer to implements the repository interface defined on the domain layer;
- And finally, the Domain Layer is independent and doesn’t implement any other module.
The “testutils” module is added using the api keyword to provide classes used on the other features’ unit tests which in my case it is only the Coroutine test rule.
And the code…
So without further ado, let’s discuss some code and how to implement it. Let’s start with the Gradle dependencies used on our project.
The first Gradle file we are going to work on is the root build.gradle. This one is fairly simple, we are going to add the hilt classpath to use the hilt plugin.
classpath "com.google.dagger:hilt-android-gradle-plugin:$versions.hilt"
PS: My versions are defined inside the libraries.gradle
Inside our app Gradle file, we are going to apply some changes, so I’m going to describe it step by step (I’m going over the necessary changes to use dynamic-features with Hilt and Dagger, so I’m going to skip things that are not related).
We need to add the Hilt plugin to declare the dependency.
id 'dagger.hilt.android.plugin'
applicationVariants.all { variant -> variant.getCompileConfiguration().exclude module: 'testutils' variant.getRuntimeConfiguration().exclude module: 'testutils'
}
If you are declaring the test utils module into your app build gradle dependencies, it is necessary to remove the “testutils” module from the final APK to avoid build failures due to merge resources, because inside my testutils I’m implementing “kotlinx-coroutines-test” and “junit-jupiter-api” that causes conflicts:
Execution failed for task ':app:app:mergeDebugJavaResource'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
> More than one file was found with OS independent path 'META-INF/AL2.0'.
The dynamic-features are automatically defined whenever a new dynamic feature is added using AS, but the following code snippet shows how to define dynamic features manually.
dynamicFeatures = [':charts:charts', ':stats:stats', ':pools:pools']
Here are all the dependencies that we will need to use dynamic features (including dynamic navigation) with Hilt and Dagger.
As you can see, we also define the new “navigation-dynamic-features-fragment”, this is pretty important to declare our navigation between modules.
Now you might be asking yourself why am I defining the dagger library since we already have Hilt. 💭
According to Google, it is still not possible to use Hilt on dynamic feature modules and the suggested approach is to use Dagger. For more info, check the documentation here.
PS: All the dependencies are defined as api to be used by the children features (Including hilt-android because of the EntryPointAccessors), except the “hilt-lifecycle-viewmodel”, which is not going to be used on other modules because we are going to use another approach to inject view models.
Feature Gradle
Whenever a new dynamic feature is created using Android Studio it already applies the appropriate plugin:
id 'com.android.dynamic-feature'
Now we must define the Kapt Dagger dependencies in our feature Gradle file.
PS: The dynamic features MUST implement the “app” project
Great, our Gradle files are all set and ready to help us implement the dynamic feature navigation and the Hilt / Dagger dependency injection. 🎉
That’s all for now and in our next post, we are going to talk about navigation.
Any thoughts ideas or suggestions, please add some comments.