Building Android MVP App in Kotlin using Rxjava2, Dagger2 ,Retrofit Tutorial

We will be using Kotlin Programming Language.

In this tutorial, we will learn MVP (Model-View-Presenter) in Android using Kotlin and We will implement Dependency Injection (DI) using Dagger2 Library, we will also implement Retrofit2 using Kotlin in our MVP Android project.

We will learn everything in the following sequence.

  1. We will learn about MVP Design Pattern.
  2. How to implement MVP in Android.
  3. Project folder structure for MVP in Android using Kotlin.
  4. MVP code sample in Kotlin.
  5. We will learn about Dependency Injection.
  6. Setup Gradle dependencies for Dagger2 and Retrofit in Android Studio for Dagger2.
  7. Implement Dagger2 (DI) in our sample MVP Android App sample project using Kotlin.
  8. Implementing Retrofit using Kotlin in our MVP Android sample project.

What is MVP Design Pattern?

Model View Presenter is an architectural pattern used to encourage/facilitate separation of concern in software projects.

MVP improves application architecture and facilitate unit testing.

MVP Architecture in Android separates business logic from view (activities/ fragments/custom views) through the presenter.

The Model:

Model layer is used to provide data to populate user interface.

Based on the business logic Model layer can provide required data from Network (through API call), from Application Shared Preferences or from SQLite Database

Model layer cannot directly communicate with View. Although it has to provide data to the View (UI) but it will do so through presenter layer.

The View:

Any Activity, Fragment, CustomView, Dialog or any other UI widget in our Android Application is considered as View.

View’s Responsibilities:
  1. Pass user interaction for example ( Button click event, touch interaction) to the presenter.
  2. The view has reference to the presenter so it will call any method in presenter based on any user interaction with the view (for example button click)
The Presenter:

The presenter is the middleman between View and Model.

Any user interaction with View is passed to Presenter, for example, a user Click on Login Button on Login Activity.

This action (login button click) is passed to the presenter. Now its presenter responsibility to validate login credentials (for example username and password).

Make Login API call and return its response to the View.

Presenter layer should not access Android API’s.

View has a reference to the Presenter.

mvp in android
MVP Android Diagram

To make sure we are implementing MVP in Android according to its ground rules:

  • View’s is only responsible to handle user interaction (e.g. Button Click in an activity) and draw UI.
  • The view is responsible to pass (delegate) user interaction to the Presenter.
  • The view cannot directly communicate with the Model layer.
  • The Presenter layer is responsible to handle interactions passed by the View and make appropriate use of Model layer and pass data back to the View using interface.
  • Model layer is responsible for handling the business logic, for example, getting data from the backend, shared preferences. And passing data to the presenter, so the presenter can pass to the View for displaying on UI.

Make sure you follow above MVP Android rules.

Let’s learn how to organize project file structure.

I have created following packages in my project.

  • data                (Data classses / POJO classes goes here)
  • di                    (Dependency Injection Code like components and modules goes here)
  • network          (API calls, API End points / Retrofit Code goes here)
  • service
  • ui                    (Activities/Fragments goes here)
  • utils
MVP in Android packages
MVP Android packages structure

We will also add Base Activity Class, all activities in our project will implement Base Activity.

What will be inside Base Activity:

BaseActivity is an abstract class the basic purpose is to put all of code which is common in all activities over here. This avoids code reputation and also result in clean and precise code.

In our case our base activity is directly inherited from AppCompatActivity. Here we can see we are implementing IView interface(It will be explained later) in our BaseActivity. Below is code for BaseActivity.

abstract class BaseActivity : AppCompatActivity(),IView {

    /**
     * A dialog showing a progress indicator and an optional text message or
     * view.
     */
    protected var mProgressDialog: ProgressDialog?=null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(setLayout())
        initialzeProgressDialoge()
        init(savedInstanceState)

    }

    fun initialzeProgressDialoge(){

        if(mProgressDialog==null) {

            mProgressDialog = ProgressDialog(this)
            mProgressDialog!!.isIndeterminate = true
            mProgressDialog!!.setCancelable(false)
        }

    }

    override fun onPostCreate(savedInstanceState: Bundle?) {
        super.onPostCreate(savedInstanceState)

    }

    override fun onResume() {
        super.onResume()
    }

    override fun onDestroy() {
        super.onDestroy()
        System.gc()
        System.runFinalization()
        dismissProgress()
        mProgressDialog=null
    }



    @LayoutRes
    abstract fun setLayout():Int
    abstract fun init(savedInstanceState: Bundle?)
    abstract fun onStartScreen()
    abstract fun stopScreen()


    fun showProgress(msgResId: Int,
                     keyListener: DialogInterface.OnKeyListener?) {
        if (isFinishing)
            return

        if (mProgressDialog!!.isShowing) {
            return
        }

        if (msgResId != 0) {
            mProgressDialog?.setMessage(resources.getString(msgResId))
        }

        if (keyListener != null) {
            mProgressDialog?.setOnKeyListener(keyListener)

        } else {
            mProgressDialog?.setCancelable(false)
        }
        mProgressDialog?.show()
    }

    /**
     * @param isCancel
     */
    fun setCancelableProgress(isCancel: Boolean) {
        if (mProgressDialog != null) {
            mProgressDialog?.setCancelable(true)
        }
    }

    /**
     * cancel progress dialog.
     */
    fun dismissProgress() {
        if (mProgressDialog != null && mProgressDialog!!.isShowing) {
            mProgressDialog?.dismiss()
        }
    }


    override fun hideLoading() {
        dismissProgress()
    }

    override fun showLoading() {
        showProgress(R.string.loading, null)
    }

    override fun loadError(e: Throwable) {
        showHttpError(e)
    }

    override fun loadError(msg: String) {
        Toast.makeText(this,msg,Toast.LENGTH_SHORT).show()
    }


    /*
    Improper handling in real case
     */

    protected fun showHttpError(e: Throwable) {
      loadError(e.localizedMessage)
    }

    override fun onStop() {
        super.onStop()
        stopScreen()
    }



}


As We are following MVP Architectures, as We discussed each Activity class in our project will have Presenter.

Our All presenters will be inherited by base presenter

What will be inside Base Presenter:

In MVP memory leak is a very common issue which is caused mostly by network calls. Imagine a scenario where we send a network request and our activity get destroyed.

Now lets see what we have in our base presenter.

We are setting the view as volatile so we could use it among different threads. We have a method to add disposals into our composite disposal. We made a method to unbind view which cleans our composite disposal and also set our view to null so GC can clean it.

open class Preseneter<V>(@Volatile var view: V? ){


    companion object {

        /*
        var compositeDisposables: CompositeDisposable
        Every method which will be part of presenter lyer will be added in it so we could dispose off them once they are no more in our use
        */
        var compositeDisposables: CompositeDisposable=CompositeDisposable()

    }


    init {


    }


    protected fun view(): V? {
        return view
    }

    @CallSuper
    fun unbindView() {
        if (compositeDisposables != null) {
            compositeDisposables.clear()
            compositeDisposables.dispose()
        }
        this.view = null
    }

    fun addDisposable(disposable: Disposable) {
        compositeDisposables.add(disposable)
    }


}

MVP in Android using Kotlin Sample code Example:

We will create Login Activity which will be following MVP (Model-View-Presenter) in Android using Kotlin.

Login Activity will be inside ui Package.

mvp for Android using Kotlin
MVP Android Login Code
  • LoginPresenter and LoginView are contractual interfaces.
  • LoginPresenter Interface will have methods which are to be implemented by LoginPresenterImpl class.
  • And LoginView will have methods for View and LoginActivity will implement LoginView.
  • LoginPresenter will first validate if User has entered valid username and password or not.
  • if username and password are invalid Presenter Layer will call a method from LoginView to inform User about the error.
  • if username and password are valid Presenter Layer is responsible to make Login API call and pass the result to the View (LoginActivity).
interface LoginPresenter {

    fun peformLogin(userName: String, userPassword: String)

    fun validateUser(userName: String, userPassword: String)
}
interface LoginView {

    fun navigateToHome()

    fun onBackPress()

    fun onPasswordError()
}
  // LoginActivity will implement LoginView Interface.

class LoginActivity : AppCompatActivity(), LoginView {

    lateinit var loginPresenter: LoginPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        // Initialize Presenter Implementation in onCreate.
        // And pass it reference to the View.
        // So Presenter can call View (LoginActivity) methods.

        loginPresenter = LoginPresenterImpl(this)
        loginPresenter.validateUser("hammad", "")

    }

    override fun onPasswordError() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun onBackPress() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun navigateToHome() {
       // START ACTIVITY INTENT CODE GOES HERE.
    }
}
  • As you can see LoginPresenterImpl implements LoginPresenter Interface.
  • And it is receiving LoginView reference through its primary constructor. so it can callback view methods.
class LoginPresenterImpl(var loginViewInit: LoginView) : LoginPresenter {

    override fun validateUser(userName: String, userPassword: String) {
       
        // This function inside presenter layer
       // will have username and password validation logic.
       
      if (userPassword=="")
        loginViewInit.onPasswordError() 
 }

 override fun peformLogin(userName: String, userPassword: String) {
      
      if (userName == "hammad") {
         loginViewInit.navigateToHome()
      }
 }
}

We are done with learning Model View Presenter (MVP) in Android using Kotlin.

Now we will implement dependency injection using the Dagger2 framework and we will make API call inside performLogin() function of our presenter using Retrofit.

Now we are at Step5 of the Android MVP using Dagger2 and Retrofit in Kotlin Tutorial.

What is Dependency Injection (DI):

Dependency Injection (DI) is a design pattern in software engineering.

It is a mechanism of providing required dependencies to other objects.

For example, You have three classes in your project. ClassA, ClassB, ClassC.

ClassC needs an object of ClassA and ClassB.

Whereas ClassB needs an object of ClassA.

Android dependency injection with mvp
Dependency Injection Example

This means ClassC depends on ClassA and ClassB. And ClassB depends on ClassA. (One class is dependent on another class / Dependency).

Now we don’t want to create objects of ClassA and ClassB inside ClassC. We want to provide/pass initialized objects of ClassA and ClassB to ClassC.

For this purpose, we will inject (provide) required objects (dependencies) to ClassC.

We will use Dagger2 Library for this purpose.

What is Dagger2 Dependency Injection (DI)?

Dagger2 is a framework which is used to provide/manage dependencies. It uses annotations.

Annotations in Dagger2 (DI):

  • @Provides
  • @Module
  • @Component
  • @Inject

@Provides Annotation in Dagger2:

@Provides annotation in Dagger is used with methods which provide the certain dependency.

For example below function, provideRetrofit is annotated with @Provides annotation (which means it will provide a dependency of type Retrofit object).

Because below function return type is Retrofit, so Dagger2 will know that whenever it needs Retrofit object it will use this function.

// Retrofit object is available for dependency injection.

@Provides
fun provideRetrofit(gson: Gson): Retrofit {
    return Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl("https://jsonplaceholder.typicode.com/").build()
}

We add functions annotated with @Provides annotation inside Module Classes.

@Module Annotation in Dagger2:

  • As mentioned earlier, We define methods in Module classes.
  • We annotate Module classes with @Module annotation.
  • And those methods provide the dependency to the Dagger Component.

Below NetModule class is annotated with @Module annotation and it can provide an object of type Retrofit, Gson, and INetworkAPI through @Provides annotated functions.

@Module
class NetModule {
      
    @Provides
    fun provideRetrofit(gson: Gson): Retrofit {
                return Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://jsonplaceholder.typicode.com/").build()
    }

    @Provides
    fun providesGson(): Gson {
        return GsonBuilder().create()
    }

    // This function need Retrofit object which we are passing in argument.
    // We will not create Retrofit object in this function.
    // Instead it will be injected/provided by Dagger2.
    // Dagger2 will get Retrofit object from provideRetrofit function declared above.
   
    @Provides
    fun provideNetworkService(retrofit: Retrofit): INetworkApi {
        return retrofit.create(INetworkApi::class.java)
    }
}

@Component Annotation in Dagger2:

  • A component is an Interface which is annotated with @Component annotation.
  • It knows about modules in your project from which it can get dependencies and provide where required by Dagger.
  • A Component knows where it can get dependencies (from which modules).
  • A Component is responsible to provide dependencies required by Dagger.
  • It is responsible to create and store objects.
// we already discussed NetModule class above.

@Component(modules = [NetModule::class])
interface ApplicationComponent {

     // this means ApplicationComponent has to provide/inject
     // required dependencies in LoginPresenterImpl class.

    fun inject(mLoginPresenterImpl: LoginPresenterImpl)
}

Now we are at Step6 of the tutorial.

Setup Gradle dependencies for Dagger2 and Retrofit in Android Studio for Dagger2.

Below is my module build.gradle file.

Don’t forget to add plugins and related dependencies.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'

android {

    kapt {
        generateStubs = true
    }
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.es.developine"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    
    //RETROFIT ...  NETWORK LIBRARY
    
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    // RETROFIT .. CONVERTER
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

    //  Dagger2 required Gradle dependencies.

    implementation 'com.google.dagger:dagger-android:2.15'
    implementation 'com.google.dagger:dagger-android-support:2.15'

    // if you use the support libraries
    kapt 'com.google.dagger:dagger-android-processor:2.15'
    kapt 'com.google.dagger:dagger-compiler:2.15'
}

Implement Dagger2 (DI) in our sample MVP Android App project using Kotlin.

Remember in the start of the tutorial, we created di package in our project.

We will add Dependency Injection (Dagger) related work like components and modules in our di package.

Create two packages, Component, and Module inside di package.

Add Interface ApplicationComponent in component package.

@Component(modules = [NetModule::class])
interface ApplicationComponent {
    fun inject(mLoginPresenterImpl: LoginPresenterImpl)
}

Create NetModule class and add it to module package.

@Module
class NetModule {

    @Provides
    fun provideRetrofit(gson: Gson): Retrofit {
        return Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://jsonplaceholder.typicode.com/").build()
    }

    @Provides
    fun providesGson(): Gson {
        return GsonBuilder().create()
    }

    @Provides
    fun provideNetworkService(retrofit: Retrofit): INetworkApi {
        return retrofit.create(INetworkApi::class.java)
    }
}

Now we have to initialize ApplicationComponent class which is responsible to provide requested dependencies.

We will initialize it in Application Class which extends Application.

open class ApplicationClass : Application() {


    public lateinit var applicationComponent: ApplicationComponent

    override fun onCreate() {
        super.onCreate()

        // ApplicationComponent is our component interface.
        // NetModule is our Module class.

        applicationComponent = DaggerApplicationComponent.builder()
                .netModule(NetModule())
                .build()

        applicationComponent.inject(this)
    }
}

Also, don’t forget to declare this ApplicationClass in Manifest file.

At this step, you might get unable to resolve DaggerApplicationComponent error in Android Studio.

Simply build -> Re-Build your project and issue will be fixed.

You have to add Internet Permission as well.

Because we are about to add Retrofit2 API call using Kotlin in our sample MVP Android App.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.es.developine">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".ApplicationClass"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".ui.login.LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

We are done with Dagger 2 along with MVP (Model-View-Presenter) in Android using Kotlin.

Implementing Retrofit using Kotlin in our MVP Android sample project:

We have already added Retrofit and Gson Gradle Dependencies.

We will hit this free API and get JSON Response.

https://jsonplaceholder.typicode.com/posts

  1. Create interface INetworkApi in network package.
  2. Create a new Class/File EndPoints in network package.

INetworkApi.kt

interface INetworkApi {

    @GET(Endpoints.posts)
    fun getAllPosts(): Observable<List<PostData>>
}

EndPoints.kt

object Endpoints {
    const val posts = "posts/"
}
showing json list recyclerview kotlin
I also created posts package.
  • In PostActivity we will make API call using Retrofit2.
  • We will automatically parse JSON response using Gson Serialization.
  • We will also implement MVP along with Dagger-2.

updated DaggerApplicationComponent.kt

@Component(modules = [AppModule::class, NetModule::class])
interface ApplicationComponent {

    fun inject(mewApplication: ApplicationClass)
    fun inject(mLoginPresenterImpl: LoginPresenterImpl)
    fun inject(mLoginActivity: LoginActivity)
    fun inject(mPostPresenterImpl: PostPresenterImpl)

}

Data Class PostData.kt (List of Posts/ API Response Model Class)

data class PostData(
      @SerializedName("userId") val userId: Int,
      @SerializedName("id") val id: Int,
      @SerializedName("title") val title: String,
      @SerializedName("body") val body: String
)

Interface PostView.kt

interface PostView {

    fun showAllPosts(postList: List<PostData>)
}

If you have any confusion, a suggestion for improvement please let me know through the comments section.

Interface PostPresenter.kt

interface PostPresenter {

    fun getAllPosts()
}

PostActivity.kt

class PostActivity : AppCompatActivity(), PostView {


    lateinit var postPresenter: PostPresenter


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_post)


        postPresenter = PostPresenterImpl(this, application)

        postPresenter.getAllPosts()
    }


    override fun showAllPosts(postList: List<PostData>) {

        Log.d("Response", "" + postList)
    }

}

Class PostPresenterImpl.kt

class PostPresenterImpl(var postView: PostView, var applicationComponent: Application) : PostPresenter {


    @Inject
    lateinit var mNetworkApi: INetworkApi

    init {
        (applicationComponent as ApplicationClass).applicationComponent.inject(this)
    }

    override fun getAllPosts() {

        var allPosts = mNetworkApi.getAllPosts()
        allPosts.subscribeOn(IoScheduler()).observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    postView.showAllPosts(it)
                }
    }
}

Conclusion:

  • We have implemented MVP Architecture in Android along with Dagger2 and Retrofit2 using Kotlin.
  • We have learned about Dependency Injection (DI) using Dagger-2 in Android and how to setup dagger in Android Studio using Kotlin.
  • We have learned about Components and Modules in Dagger2 in Android using Kotlin.
  • We learned about MVP in Android using Kotlin and implemented two examples. LoginActivity and PostActivity.
  • We have learned how to integrate Retrofit with MVP and Dagger2 in Android using Kotlin.

Github:

Recommended Reading:

Contact Us