loading...
Cover image for Best ways to organise your Android Dependencies

Best ways to organise your Android Dependencies

ashkay profile image Ashish Kumar Updated on ・4 min read

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 by:

ashkay profile

Ashish Kumar

@ashkay

Hi, I am a Computer Science student. I do Android Development, Full Stack Web Development and also Game Development 😁

Discussion

markdown guide
 

Great article, It's very important to organize your dependencies. thanks for giving credits.