Best ways to organise your Android Dependencies

ashkay

Ashish Kumar

Posted on April 29, 2020

Best ways to organise your Android Dependencies

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.+"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"

   ]
}
Enter fullscreen mode Exit fullscreen mode

And now import it into build.gradle like

dependencies {
    implementation supportDependencies.design
    implementation supportDependencies.cardview
    implementation supportDependencies.recyclerview
}
Enter fullscreen mode Exit fullscreen mode

or we can also do it in single line

dependencies {
    implementation supportDependencies.value()
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode
//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}"
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

}
Enter fullscreen mode Exit fullscreen mode

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
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

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 😎.

💖 💪 🙅 🚩
ashkay
Ashish Kumar

Posted on April 29, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related