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.
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.
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.
Any Activity, Fragment, CustomView, Dialog or any other UI widget in our Android Application is considered as View.
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.
To make sure we are implementing MVP in Android according to its ground rules:
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.
We will also add Base Activity Class, all activities in our project will implement 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
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)
}
}
We will create Login Activity which will be following MVP (Model-View-Presenter) in Android using Kotlin.
Login Activity will be inside ui Package.
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.
}
}
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.
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.
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.
Dagger2 is a framework which is used to provide/manage dependencies. It uses annotations.
@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.
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)
}
}
// 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.
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'
}
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.
We have already added Retrofit and Gson Gradle Dependencies.
We will hit this free API and get JSON Response.
https://jsonplaceholder.typicode.com/posts
INetworkApi.kt
interface INetworkApi {
@GET(Endpoints.posts)
fun getAllPosts(): Observable<List<PostData>>
}
EndPoints.kt
object Endpoints {
const val posts = "posts/"
}
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)
}
}
}