Android simple MVVM example

 This is a demonstration of using Android architectural component ViewModel for a simple MVVM design pattern. It will use RxJava 2, RxKotlin, RxAndroid, Retrofit 2, etc. Github api will be used as example for calling REST service with Retrofit 2. https://api.github.com/users/google

1. Include this at the top of the app gradle file for kotlin compiler.

apply plugin: 'kotlin-kapt'

2. Added these dependencies in the app gradle file.

  //Architecture component
  implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  kapt 'androidx.lifecycle:lifecycle-compiler:2.0.0'


  //RxJava
  implementation "io.reactivex.rxjava2:rxandroid:2.0.1"
  implementation "io.reactivex.rxjava2:rxjava:2.1.3"
  implementation "io.reactivex.rxjava2:rxkotlin:2.1.0"

  // Retrofit 2
  implementation 'com.squareup.retrofit2:retrofit:2.5.0'
  implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
  implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
  implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
  implementation 'com.google.code.gson:gson:2.8.2'

3. Create utility classes for creating Retrofit rest service interface, repository and ViewModel factories.
RestUtil.kt

class RestUtil private constructor() {
    private val API_BASE_URL = "https://api.github.com/"
    val retrofit: Retrofit

    init {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        val httpClient = OkHttpClient.Builder().addInterceptor(interceptor).build()

        val builder = Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())

        retrofit = builder.client(httpClient).build()
    }

    companion object {
        private var self: RestUtil? = null

        val instance: RestUtil
            get() {
                if (self == null) {
                    synchronized(RestUtil::class.java) {
                        if (self == null) {
                            self =
                                    RestUtil()
                        }
                    }
                }
                return self!!
            }
    }

}

RepositoryFactory.kt

object RepositoryFactory {

    fun createGithubRepository() : GithubRespository {
        val githubApi = RestUtil.instance.retrofit.create(GithubApi::class.java)
        return GithubRespository(githubApi)
    }

}

ViewModelFactory.kt

class ViewModelFactory(private val githubRespository: GithubRespository) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainActivityViewModel(githubRespository) as T
    }

}

4. Create data model, retrofit api interface, and repository.
GithubAccount.kt

data class GithubAccount(
    @SerializedName("login") var login : String = "",
    @SerializedName("id") var id : Int = 0,
    @SerializedName("created_at") var createdAt : String = "",
    @SerializedName("updated_at") var updatedAt : String = "")

GithubApi.kt

interface GithubApi {
    @GET("/users/{username}")
    fun getGithubAccount(@Path("username") username: String): Single<Response<GithubAccount>>
}

GithubRespository.kt

class GithubRespository(val githubApi: GithubApi) {

    fun fetchGithubAccount(name : String) : Observable<GithubAccount> {
        return Observable.create { emitter ->

            githubApi.getGithubAccount(name)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe( {
                    if (it.body() != null) {
                        emitter.onNext(it.body()!!)
                    }
                }, {
                    it.printStackTrace()
                })

        }
    }

}

5. Create the ViewModel which uses repository to get data and emit the data to the view through LiveData.

class MainActivityViewModel(val githubRespository: GithubRespository) : ViewModel() {

    private val _githubAccount : MutableLiveData<GithubAccount> = MutableLiveData()
    val githubAccount : LiveData<GithubAccount> = _githubAccount

    fun getGithubAccount(name : String) {
        githubRespository
            .fetchGithubAccount(name)
            .subscribe {
                _githubAccount.postValue(it)
            }
    }

}

6. Finally using the ViewModel in the View class, MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var mainActivityViewModel : MainActivityViewModel

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

        mainActivityViewModel = ViewModelProviders.of(this, ViewModelFactory(RepositoryFactory.createGithubRepository())).get(MainActivityViewModel::class.java)

        mainActivityViewModel.githubAccount.observe(this, Observer {
            tv_content.text = it.toString()
        })


        mainActivityViewModel.getGithubAccount("google")
    }
}

7. Make sure add the INTERNET permission in the manifest file.

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

8. The layout file for the MainActivity, activity_main.xml

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

    <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""
            android:gravity="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

No comments:

Post a Comment

Note: only a member of this blog may post a comment.

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...