BasicSample ( MVVM + Dagger +Retrofit + Jetpack )

The purpose of this post is to provide a basic introduction to the Model-View-ViewModel (MVVM), Dagger, Retrofit and Jetpack.

BasicSample ( MVVM + Dagger +Retrofit + Jetpack )

BasicSample ( MVVM + Dagger +Retrofit )

Download sample code .  BasicSample 

Sample APK : https://github.com/datanapps/BasicExample-kotlin-mvvm-DI-/blob/master/appdetail/apks/release/app-release.apk

The purpose of this post is to provide an introduction to the Model-View-ViewModel (MVVM) pattern. While I’ve participated in lots of discussions online about MVVM, it occurred to me that beginners who are learning the pattern have very little to go on and a lot of conflicting resources to wade through in order to try to implement it in their own code.

 

----------------------------------------------------------------

Creating a sample project in android studio

1. Create a sample project from android studio. to create a sample project please go to below steps:

A. File  >> New  >> New Project >> A popup will come then select an 'Empty Activity'.

B. Select Project name, package name, SDK Version and Language Kotlin.

 

2. Now add dependencies in app gradle file. 

 

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: "kotlin-kapt"

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.datanapps.basicexample"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    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'


    // dagger 2
    implementation "com.google.dagger:dagger-android:2.16"
    kapt "com.google.dagger:dagger-compiler:2.16"
    kapt "com.google.dagger:dagger-android-processor:2.16"

    // ROOM API
    implementation "androidx.room:room-runtime:2.2.2"
    kapt "androidx.room:room-compiler:2.2.2"

    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.6.1'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
    // gson convertor
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

    // recycle view
    implementation 'androidx.recyclerview:recyclerview:1.1.0'

    // material design
    implementation 'com.google.android.material:material:1.0.0'

    // glide
    implementation 'com.github.bumptech.glide:glide:4.10.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
}

 

3.  Create Model for User, this is API result base class : 

BaseUsers.kt

package com.datanapps.network.model
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

class BaseUsers {
    @SerializedName("userlist")
    @Expose
    var userlist: MutableList<User>? = null
    @SerializedName("totalrecords")
    @Expose
    var totalrecords: Int? = null
    @SerializedName("message")
    @Expose
    var message: String? = null
}

 

4.  Create Model for User :

User.kt 

package com.datanapps.network.model

import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

class User {
    @SerializedName("id")
    @Expose
    var id: String? = null
    @SerializedName("FirstName")
    @Expose
    var firstName: String? = null
    @SerializedName("LastName")
    @Expose
    var lastName: String? = null
    @SerializedName("Email")
    @Expose
    var email: String? = null
    @SerializedName("ContactNo")
    @Expose
    var contactNo: String? = null
    @SerializedName("DOB")
    @Expose
    var dob: String? = null
    @SerializedName("Image")
    @Expose
    var image: String? = null
}

 

5.  Create ENUM class for NETWORK status:

NetworkStatus.kt 

package com.datanapps.network


// network status for API
enum class NetworkStatus {
    SUCCESS,
    INTERNET_CONNECTION,
    FAIL,
    SERVER_ERROR,
    NO_RECORDS
}

 

6.  Create Retrofit Interface API

UserDataAPI.kt

 

package com.datanapps.network

import com.datanapps.network.model.BaseUsers
import retrofit2.Call
import retrofit2.http.GET


interface UserDataAPI{
    /**
     * Get the list of the pots from the API
     */
//https://datanapps.com/DNARestAPIs/getUserLists
    @GET("DNARestAPIs/getUserLists")
    fun getNetworkDataList(): Call<BaseUsers>

}

7.  Now this time to move dagger part and create activity module.

ActivitiesModule.kt

 

@Module
abstract class ActivitiesModule {
    @ContributesAndroidInjector
    abstract fun contributeMainActivity(): UserDataActivity
}

 

8.  Now create network module.

NetworkModule.kt

 

package com.datanapps.di.modules

import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import com.datanapps.network.UserDataAPI
import dagger.Module
import dagger.Provides
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Inject
import javax.inject.Singleton


@Module
class NetworkModule @Inject constructor(var application: Application) {


    /**
     * Provides the Post service implementation.
     * @param retrofit the Retrofit object used to instantiate the service
     * @return the Post service implementation.
     */

    @Provides
    fun application(): Application {
        return application
    }


    val isConnected : Boolean    get(){
        val connectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            val network = connectivityManager.activeNetwork
            val capabilities = connectivityManager.getNetworkCapabilities(network)
            capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
        } else {
            connectivityManager.activeNetworkInfo.type == ConnectivityManager.TYPE_WIFI
        }
    }


    @Provides
    fun provideUserDataApi(retrofit: Retrofit): UserDataAPI =
        retrofit.create(UserDataAPI::class.java)


    /**
     * Provides the Retrofit object.
     * @return the Retrofit object
     */
    @Provides
    @Singleton
    fun provideRetrofitInterface(): Retrofit {
        val cacheSize = (5 * 1024 * 1024).toLong()
        val myCache = Cache(application.cacheDir, cacheSize)


        // for caching

        val okHttpClient = OkHttpClient.Builder()
            .cache(myCache)
            // add off line interceptor
            .addInterceptor { chain ->
                var request = chain.request()
                request = if (isConnected)
                    request.newBuilder().header("Cache-Control", "public, max-age=" + 5).build()
                else
                    request.newBuilder().header(
                        "Cache-Control",
                        "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7
                    ).build()
                chain.proceed(request)
            }
            // add on line interceptor
            .addNetworkInterceptor {

                val response: Response = it.proceed(it.request())
                val maxAge =
                    60 // read from cache for 60 seconds even if there is internet connection

                response.newBuilder()
                    .header("Cache-Control", "public, max-age=$maxAge")
                    .removeHeader("Pragma")
                    .build()
            }
            .build()


        // create retrofit
        return Retrofit.Builder()
            .baseUrl("https://datanapps.com")
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }


}

 

9. Create Dagger component to manage module and provider.

 AppComponent.kt

package com.datanapps.di.components


import com.datanapps.MyApplication
import com.datanapps.di.modules.ActivitiesModule
import com.datanapps.di.modules.NetworkModule
import dagger.Component

@Component(
    modules = [
        ActivitiesModule::class, NetworkModule::class
    ]
)
interface AppComponent {

    fun inject(myApplication: MyApplication)
    fun inject (networkModule: NetworkModule)

}

10. Create Application class and initialise dagger and module.

MyApplication.kt 

package com.datanapps

import android.app.Activity
import android.app.Application
import com.datanapps.di.components.DaggerAppComponent
import com.datanapps.di.modules.NetworkModule

import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import javax.inject.Inject


class MyApplication : Application(), HasActivityInjector {

    @Inject
    lateinit var activityInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector(): AndroidInjector<Activity> {
        return activityInjector
    }

    override fun onCreate() {
        super.onCreate()

       // added dagger
          DaggerAppComponent.builder()
            .networkModule(NetworkModule(this))
            .build()
            .inject(this)


    }
}

 

11. Create a layout for main activity to display all user in list. 

activity_user_data.xml

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.datanapps.userInterface.UserDataActivity">

    <androidx.core.widget.ContentLoadingProgressBar
        android:id="@+id/networkDataProgressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:progress="25"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycleViewNetworkData"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

 

12. Create xml to display user in adapter layout user

layout_user_list.xml

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/dp_10"
    >

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/ivUserImage"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:visibility="visible"
        android:padding="@dimen/dp_5"
        android:textColor="@color/colorAccent"
        app:srcCompat="@drawable/ic_down_arrow" />


    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvLayoutName"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:textStyle="bold"
        android:layout_marginStart="@dimen/dp_10"
        app:layout_constraintStart_toEndOf="@+id/ivUserImage"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:textColor="@color/colorAccent"
        tools:text="@string/app_name" />

</androidx.constraintlayout.widget.ConstraintLayout>

13. Create class for User data list :
 

UserDataActivity.kt

 

package com.datanapps.userInterface

import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.DividerItemDecoration
import com.google.android.material.snackbar.Snackbar
import com.sph.sgnetworkdata.R
import com.datanapps.network.NetworkStatus
import com.datanapps.network.model.BaseUsers
import dagger.android.AndroidInjection
import kotlinx.android.synthetic.main.activity_user_data.*
import javax.inject.Inject


class UserDataActivity : AppCompatActivity() {

    @Inject
    lateinit var networkDataActivityViewModel: UserDataActivityViewModel

    @Inject
    lateinit var networkDataAdapter: UserDataAdapter


    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_data)
        prepareView()
        callApiAndUpdateUI()
    }

    private fun prepareView() {
        recycleViewNetworkData.itemAnimator = DefaultItemAnimator()
        recycleViewNetworkData.addItemDecoration(
            DividerItemDecoration(
                this,
                DividerItemDecoration.VERTICAL
            )
        )
        this.recycleViewNetworkData.adapter = networkDataAdapter
        networkDataAdapter.onItemClick = { user ->

            // do something with your item

          /* val bottomSheetFragment =
                UserDataDetailFragment(user)
            bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag)*/
        }

    }

    private fun callApiAndUpdateUI() {
        networkDataProgressBar.visibility = View.VISIBLE
        networkDataActivityViewModel.networkLiveData.observe(this,
            Observer<BaseUsers> { baseDataStore ->
                networkDataProgressBar.visibility = View.GONE

                if (baseDataStore != null) {
                    networkDataAdapter.addItemList(baseDataStore.userlist!!)
                }
                else{
                    networkDataActivityViewModel.statusLiveData.value = NetworkStatus.FAIL
                }
            })


        // status of actions
        networkDataActivityViewModel.statusLiveData.observe(this,
            Observer<NetworkStatus> { status ->
                networkDataProgressBar.visibility = View.GONE
                onNetWorkStateChanged(status)
            })
        networkDataActivityViewModel.fetchDataDetail()
    }


    private fun onNetWorkStateChanged(state: NetworkStatus) = when (state) {
        NetworkStatus.INTERNET_CONNECTION -> showSnackBar(getString(R.string.msg_no_internet_network))
        NetworkStatus.SERVER_ERROR -> showSnackBar(getString(R.string.msg_server_error))
        NetworkStatus.FAIL -> showSnackBar(getString(R.string.msg_something_went_wrong))
        NetworkStatus.NO_RECORDS -> showSnackBar(getString(R.string.msg_no_records))

        else -> showSnackBar(getString(R.string.msg_unknown))
    }
    private fun showSnackBar(msg: String) {
        Snackbar.make(findViewById(android.R.id.content), msg, Snackbar.LENGTH_SHORT)
            .show()
    }

}

 

14. Create a view model to handle all process and flow of activities.
 

UserDataActivityViewModel.kt

package com.datanapps.userInterface

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.datanapps.di.modules.NetworkModule
import com.datanapps.network.NetworkStatus
import com.datanapps.network.model.BaseUsers
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject


class UserDataActivityViewModel @Inject constructor() : ViewModel() {

    @Inject
    lateinit var networkModule: NetworkModule

    val networkLiveData = MutableLiveData<BaseUsers>()
    val statusLiveData = MutableLiveData<NetworkStatus>()


    fun fetchDataDetail() {

        val networkDataAPI =
            networkModule.provideUserDataApi(networkModule.provideRetrofitInterface())

        val networkDataAPICall =
            networkDataAPI.getNetworkDataList()

        networkDataAPICall.enqueue(object : Callback<BaseUsers> {

            override fun onResponse(
                call: Call<BaseUsers>?,
                response: Response<BaseUsers>?
            ) {
                if(response!!.body()!=null)
                     networkLiveData.value = response?.body()
                else{
                    statusLiveData.value = NetworkStatus.FAIL
                }
            }

            override fun onFailure(call: Call<BaseUsers>?, t: Throwable?) {
                statusLiveData.value = NetworkStatus.FAIL
            }
        })

        if(!networkModule.isConnected) {
            // off line
            statusLiveData.value = NetworkStatus.INTERNET_CONNECTION
        }

    }

}

15. This is adapter class to iterate user data and displlsy into row. 

UserDataAdapter.kt

package com.datanapps.userInterface

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.datanapps.network.model.User
import com.sph.sgnetworkdata.R
import kotlinx.android.synthetic.main.layout_user_list.view.*
import javax.inject.Inject


class UserDataAdapter @Inject constructor():
    RecyclerView.Adapter<UserDataAdapter.NetworkDataViewHolder>() {

    private var networkDataList = mutableListOf<User>()

    var onItemClick: ((recordList: User) -> Unit)? = null

    var context: Context? = null
    var layoutInflater: LayoutInflater? = null

    fun addItemList(networkDataList: MutableList<User>) {
        this.networkDataList = networkDataList
        notifyDataSetChanged()
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NetworkDataViewHolder {

        if (layoutInflater == null) {
            context = parent.context
            layoutInflater = LayoutInflater.from(parent.context)
        }

        val itemView = layoutInflater!!
            .inflate(R.layout.layout_user_list, parent, false)
        return NetworkDataViewHolder(itemView)
    }


    override fun onBindViewHolder(holder: NetworkDataViewHolder, position: Int) {
        holder.bindView(networkDataList.get(position))
    }

    override fun getItemCount(): Int {
        return networkDataList.size
    }


    inner class NetworkDataViewHolder(view: View) : RecyclerView.ViewHolder(view) {

        fun bindView(user: User) {
            itemView.tvLayoutName.text = user.firstName

            Glide.with(context!!).load(user.image).into(itemView.ivUserImage);

            itemView.setOnClickListener {
                onItemClick?.invoke(user)
            }
        }


    }
}

 

Hope its work now, Please let me know in commect section.

Read More : https://proandroiddev.com/mvvm-architecture-viewmodel-and-livedata-part-1-604f50cda1

-------------------------------------------------------------

Also, check out our Blogging for developers : 
Android ROOM API

But in the meantime, you can suggest me in comments for next ' Title '. please share your thoughts, tips with us, I'm sure you have a good points so please comment below or mail us at datanapps@gmail.com.

Please If you enjoyed this post, I’d be very grateful if you’d help it spread by emailing it to a friend, or sharing it on Twitter or Facebook.

Thank you so much, Have a great day !

Next week I’ll post   Dagger 2  which is important for us in daily life.


Click Here To See More

What's Your Reaction?

like
1
dislike
0
love
1
funny
1
angry
0
sad
1
wow
1