Android App Modularization: 4 Useful Tips to Start
Modularizing an Android app means breaking it into self-contained modules that can be assembled to form the complete app.
Join the DZone community and get the full member experience.
Join For FreeAndroid app modularization refers to the process of breaking down an Android app into smaller, independent modules. These modules can be thought of as building blocks that can be combined to form the complete app.
Each module is typically responsible for a specific feature or functionality of the app and can be developed, tested, and deployed independently. Modularizing an app can provide several benefits, including easier maintenance, faster development cycles, improved scalability, and enhanced performance.
By isolating changes to specific modules, developers can make updates more efficiently and avoid unintended side effects. Furthermore, modularization can help reduce the app’s size and improve its overall performance by allowing users to download only the necessary components. In summary, modularization is a powerful technique that can help developers create more efficient, scalable, and maintainable Android apps.
Context
We work with a legacy project whose development started more than four years ago by another company with other standards, practices, and experiences.
In this project, we work with two weeks sprints, always delivering new features and fixing bugs, with very closed scopes where adding technical tasks is difficult. We have been adding common best practices like testing and clean architecture, and we have also added new elements within the Android development environment, like Room or Jetpack Compose.
Now we start to face a need: we need to focus on Android app modularization to get its advantages.
Android App Modularization: Why Do It?
Android App Modularization can bring several benefits to your application’s maintenance, scalability, and development process.
- Faster builds. Gradle speeds up the compilation time of your project by allowing you to do tasks in parallel and reuse all the code already compiled without modifying it. When you start a new project, you may not appreciate the compilation time, but when your project grows, it is common to have compilation times of around five minutes. To adjust small details of the view, you must compile your project between three and five times, thus accumulating dead time.
- It tends to simplify development. Working in small modules focused on a single functionality makes it easier to correct, maintain and improve the code. Just by limiting the size of the code, reviewing or adding tests to start a refactor becomes more comfortable.
- Reuse modules between apps. Perhaps one of your modules can easily become a library, and you can open it to the open-source community with all the benefits without affecting your entire app, both in terms of security and integration.
- It makes it easier to add more people to the team. By diving your code into smaller modules, you can assign people or teams to them directly without affecting the rest of the app. For example, if you have a chat feature in your Android app, and you need help to reach a deadline with a functionality, just having to understand that module without having to understand the rest of the app makes it much faster for you to receive or give help.
- Improved test automation. By being able to test modules without having to do the whole flow, tests will go faster, and errors from previous steps are not propagated. You can create specific rules in your CI to speed up your compilation time; for example, only activate UI tests, usually the slowest ones, when merging to your main development branch.
Android App Modularization: Tips to Start
How can we start our Android app modularization without stopping development or affecting our ability to deliver?
When facing such a big challenge, we tend to get overwhelmed, but the most important advice is to have patience. In the end, we will find the first step of the way, and gradually we will be adding modules until we have it completely modularized.
In this article, we are not looking for a perfect recipe for how to modularize your app. Every project is different, and approaching it with strict steps can be more of a problem than a solution. For example, if we were told that in our project, we will start with the networking part, it would be chaotic, and we would fail in the attempt to see that we have some dependencies in analytics and error display. Just to create these two modules that will use networking, we could lose a couple of weeks to have everything tested and make sure we have not broken anything. And back to the context, we don’t have these two weeks to focus on technical tasks. We must adapt to our situation and look for ways to add small modules to be able to address networking and do it in the typical sprint endings with some space for external tasks.
1. Analyze Your Dependencies
Look for a small functionality with few dependencies or, better yet, none. In our case, it was all the functional programming part and all the Kotlin extensions we have created over the years. We created the module, moved all the code there, tested included, and compiled. Eureka! It worked! Easy? Well, we had to change all the imports of the project, more than 100 modified files, and check that we had not broken any test and that all the functionalities of the app were still intact, but we had our first module.
2. Test Before Your Start
A good practice we should have in our projects would be to have a good test base, but sometimes we get to a project which is not like that. Therefore, if we find that small functionality without dependencies, before moving it to the new module, we will create some tests that wrap and migrate everything to the new module and hopefully change imports. We were able to do the first module because we already had a good base of tests that covered this part.
3. Using Gradle to Compose Dependencies
One of the drawbacks that we can find when modularizing our project can be the management of dependencies and versions. It is often the typical error of compilation of version conflicts of the Android X library. To do this, we can take advantage of Gradle to compose our build.gradle files. In our project, we have a dependencies.gradle file where we first define the versions of our dependencies and then the dependencies themselves.
versions = [
"koin_version" : "3.1.4",
]
libs = [
"koin" : "io.insert-koin:koin-android:$versions.koin_version",
"koinCore" : "io.insert-koin:koin-core:$versions.koin_version",
"koinJavaCompat" : "io.insert-koin:koin-android-compat:$versions.koin_version",
"koinCompose" : "io.insert-koin:koin-androidx-compose:$versions.koin_version",
]
As you can see, when using koin, we have several dependencies that share versions, and if we had to update it to solve some error, we only have to come here and not look in all the Gradle files that we have in each module of the project.
But, if we have many modules, we will have many build.gradle files and we can take advantage of the composition to save a lot of repeated code. If we define a file with the basic configuration of all the Android modules, the only thing we will have to add in each file is a line of code and the particular dependencies of that one.
Android_lib.gradle
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-kapt'
apply from: "$rootDir/buildsystem/dependencies.gradle"
android {
compileSdkVersion configs.compileSdk
defaultConfig {
targetSdkVersion configs.targetSdk
minSdkVersion configs.minSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
debug {
debuggable = true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
androidTest.java.srcDirs += 'src/androidTest/kotlin'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.0.1'
}
}
With this definition, we can compose our dependencies in the new modules.
module.gradle
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
apply from: "$rootDir/buildsystem/android_lib.gradle"
android {
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.0.1'
}
}
dependencies {
implementation libs.core
implementation libs.appcompat
implementation libs.material
//COMPOSE
implementation libs.composeActivities
implementation libs.composeMaterial
implementation libs.composeConstraintLayout
implementation libs.composeTooling
implementation libs.composeRuntime
implementation libs.composeViewModel
//Room
implementation libs.roomRuntime
implementation libs.roomKtx
kapt libs.roomCompiler
//Koin
implementation libs.koin
implementation libs.koinJavaCompat
implementation libs.koinCompose
implementation libs.koinCore
//TEST
testImplementation testLibs.junit
testImplementation testLibs.coroutinesTest
}
We can even take advantage of Gradle to compose even more dependencies and not have to add or update many files when adding a new one shared by several modules.
4. Use the Readme as a Knowledge Base
Within our repositories, the Readme.md file usually contains steps to compile the project; it is the most common in closed projects. The most elaborated documentation is usually found in external solutions such as Confluence, Notion, etc. An interesting option is to have in each module a Readme file that contains some basic information. This information can be a description of the module’s functionality, its dependencies on other modules (with their relative link to facilitate navigability), relevant external dependencies, possible improvements to be made, etc. This makes it easier for new members to join the team; just by giving them access to the repository, they can navigate through the simple documentation without having to navigate through code that is much more confusing and intimidating. One consideration is that Android Studio, at the time of writing this, does not have good MD support. A good alternative is Visual Studio Code, where you can preview how your file would look in the repository.
Conclusion
In conclusion, Android app modularization can be a daunting task, but it is a necessary one if you want to improve its performance, scalability, and maintainability. By following the tips outlined in this article, such as identifying the app’s key features and breaking them down into smaller modules, you can make the process more manageable. Additionally, utilizing dependency injection, decoupling components, and establishing clear boundaries between modules can help ensure that the modules are independent and can be developed, tested, and deployed separately.
With these techniques in mind, you can modularize your old Android app and unlock a whole host of benefits, including faster development cycles, easier maintenance, and improved performance. So, don’t be afraid to take on the challenge of modularization and transform your app into a more efficient and scalable product.
Published at DZone with permission of Albert Gonzalez. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments