Best ways to organise your Android Dependencies
Ashish Kumar
Posted on April 29, 2020
We all have worked or atleast seen a project with build.gradle file which looks something like this
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.3"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:2.1"
implementation "androidx.room:room-runtime:1.3"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.google.android.material:material:1.0.0-rc01"
implementation "androidx.cardview:cardview:1.0.0"
implementation "org.jetbrains.anko:anko-commons:0.10.4"
implementation 'com.android.support:design:23.1.1'
implementation 'com.jakewharton:butterknife:7.0.1'
implementation 'com.jakewharton.timber:timber:4.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.0.0'
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:1.+"
Its very hard to find or update a dependency in this Bloated file, and this file will get worse as the development progresses.
Fixing the mess
1. Naive Approach
We can clean up the mess by creating separate variable for the versions of dependencies used.
def lifecycle_version = "2.0.0"
def room_version = "2.1.0-rc01"
def anko_version = "0.10.4"
def room_version = "2.1.0-rc01"
def material_design_version = "1.0.0-rc01"
def cardview_version = "1.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
//Room
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
//Design
implementation "com.google.android.material:material:$material_design_version"
//CardView
implementation "androidx.cardview:cardview:$cardview_version"
//Anko
implementation "org.jetbrains.anko:anko-commons:$anko_version"
This is better than before but still file can get cluttered when new dependencies gets added, there is still room for improvements.
2. Better Approach
We can create a separate file for storing our dependencies and name it something like deps.gradle
ext {
def supportVersion = "27.1.1"
supportDependencies = [
design : "com.android.support:design:$supportVersion",
cardview : "com.android.support:cardview-v7:$supportVersion",
recyclerview : "com.android.support:recyclerview-v7:$supportVersion"
]
}
And now import it into build.gradle like
dependencies {
implementation supportDependencies.design
implementation supportDependencies.cardview
implementation supportDependencies.recyclerview
}
or we can also do it in single line
dependencies {
implementation supportDependencies.value()
}
3. Recommended Approach
This approach is best suited for Project with multiple modules.
First we have to create a directory inside root project named buildSrc and add Dependencies.kt and Version.kt, these files will hold information about all dependencies in our project, Directory structure should look like this
<project>
├── buildSrc
│ └── src
│ └── main
│ └── java
│ └── Dependencies.kt
│ └── Version.kt
Now as the name suggest we will use Dependencies.kt to store all the dependencies and Version.kt to store versions.
Our lets look what our files will look like from inside.
//Version.kt will hold all the versions like this
object Version {
// android configuration
const val buildTools = "29.0.3"
const val compileSdk = 29
const val minSdk = 23
const val targetSdk = 29
const val versionCode = 1
const val versionName = "1.0"
//Libraries
const val supportLib = "28.0.0"
const val recyclerView = "1.0.0"
const val androidx = "1.0.0"
const val materialDesign = "1.0.0-rc01"
const val mockito = "1.10.19"
const val dagger2 = "2.21"
const val room = "2.0.0-rc01"
}
//Dependencies.kt will hold all libraries like this
object Dependencies {
//path to common dependencies (disscussed later)
private const val path = "../commonFiles/gradleScript/"
const val common = "${path}common.gradle"
//path to local dependencies (disscussed later)
const val dependency = "./gradleScript/dependencies.gradle"
object Module {
//Add your modules here
const val data = ":data"
const val cache = ":cache"
const val remote = ":remote"
}
//Create object for every libraries being used to group all related dependencies together
object Chucker {
const val debug = "com.github.ChuckerTeam.Chucker:library:${Version.chucker}"
const val release = "com.github.ChuckerTeam.Chucker:library-no-op:${Version.chucker}"
}
object Facebook {
const val stetho = "com.facebook.stetho:stetho:${Version.stetho}"
const val stethoNetwork = "com.facebook.stetho:stetho-okhttp3:${Version.stetho}"
}
object NavigationComponent {
const val fragment = "androidx.navigation:navigation-fragment-ktx:${Version.navigation}"
const val ui = "androidx.navigation:navigation-ui-ktx:${Version.navigation}"
}
}
We are done with Dependencies.kt and Version.kt.
Now, Create a directory again under our root project to store our common dependencies, we will call it commonFiles, and create common.gradle inside gradleScript
<project>
├── commonFiles
│ └── gradleScript
│ └── common.gradle
Create a directory gradleScript in every module you are going to use, which will store all Dependencies related to that module add file dependencies.gradle in it.
<module>
│ └── gradleScript
│ └── dependencies.gradle
Let work with our common dependencies, it should look like this.
//common.gradle
import dependencies.Dependencies
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation Dependencies.Kotlin.kotlin_stdlib_jdk7
testImplementation Dependencies.Test.test_junit
testImplementation Dependencies.Test.roboElectric
testImplementation Dependencies.Test.android_test_room
testImplementation Dependencies.Test.mockito
}
So we just simply access our dependencies from the Dependencies object we created before. Similarly in our local dependencies we can do
//dependencies.gradle
import dependencies.Dependencies
// applying common deps into local deps
apply from: Dependencies.common
dependencies {
//App Module ( App Module ONLY TO BE INCLUDED IN MAIN APP MODULE)
implementation project(Dependencies.Module.domain)
implementation project(Dependencies.Module.cache)
implementation project(Dependencies.Module.presentaion)
//RxJava
implementation Dependencies.RxJava.rxAndroid
implementation Dependencies.RxJava.rxjava2
implementation Dependencies.RxJava.rxBinding
//Dagger2
implementation Dependencies.Dagger.dagger2
implementation Dependencies.Dagger.daggerAndroid
implementation Dependencies.Dagger.daggerAndroidSupport
kapt Dependencies.Dagger.processor
kapt Dependencies.Dagger.compiler
}
Now the Final Step in every module level build.gradle we have to import the dependencies.gradle, it should look something like this
//build.gradle (local)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "kotlin-kapt"
//this will apply all the local deps stored in local dependencies.gradle
apply from: Dependencies.dependency
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.test.project"
minSdkVersion Version.minSdk
targetSdkVersion Version.targetSdk
versionCode Version.versionCode
versionName Version.versionName
}
...
}
That is it we have made workflow that is easily salable for large project and is easily manageable
Simple, clean and powerful isn’t it !!!
Just by applying small effort we have made our code more readable and manageable.
Article inspired by : https://github.com/happysingh23828/Android-Clean-Architecture
Stay Cool 😎.
Posted on April 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.