Simple Android MVVM using Android Architecture component and Kotlin

3 minute read

Published:

All the type in android development now is MVVM but since Google announced at I/O the new Architecture Components for building a robust MVVM architecture and people got confused. so here I’ll talk about MVVM architecture and how to make use of Architecture component using Kotlin.

As an example, I am going to use a simple app, that login into the API and get Success response.

mvvm pattern

1 . Model

  • Represents the Data + State + Business logic. It is not tied to the view nor to the controller, which makes it reusable in many contexts.
data class User(val first_name: String, val last_name: String, val personal_image: String) {
}

2 . View

  • Binds to observable variables and actions exposed by the View Model. It is possible for multiple views to bind to a single View Model.
class LoginActivity : AppCompatActivity() {

    var binding: ActivityLoginBinding? = null
    var viewmodel: LoginViewModel? = null
    var customeProgressDialog: CustomeProgressDialog? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
        viewmodel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
        binding?.viewmodel = viewmodel
        customeProgressDialog = CustomeProgressDialog(this)
        initObservables()


    }

    private fun initObservables() {
        viewmodel?.progressDialog?.observe(this, Observer {
            if (it!!) customeProgressDialog?.show() else customeProgressDialog?.dismiss()
        })

        viewmodel?.userLogin?.observe(this, Observer { user ->
            Toast.makeText(this, "welcome, ${user?.last_name}", Toast.LENGTH_LONG).show()
        })
    }


}

3 . ViewModel

  • Responsible for wrapping the model and preparing observable data needed by the view. It also provides hooks for the view to pass events to the model. An important thing to keep in mind is that the View Model is not tied to the view.

but before going to the viewmodel you need to know about :

  • Live Data : is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

  • Single Live Event case for handling single actions.

  • Data Binding : The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.

now we’re gonna move to the most important part.

class LoginViewModel(application: Application) : AndroidViewModel(application), Callback<User> {


    var btnSelected: ObservableBoolean? = null
    var email: ObservableField<String>? = null
    var password: ObservableField<String>? = null
    var progressDialog: SingleLiveEvent<Boolean>? = null
    var userLogin: MutableLiveData<User>? = null

    init {
        btnSelected = ObservableBoolean(false)
        progressDialog = SingleLiveEvent<Boolean>()
        email = ObservableField("")
        password = ObservableField("")
        userLogin = MutableLiveData<User>()
    }

    fun onEmailChanged(s: CharSequence, start: Int, befor: Int, count: Int) {
        btnSelected?.set(Util.isEmailValid(s.toString()) && password?.get()!!.length >= 8)


    }

    fun onPasswordChanged(s: CharSequence, start: Int, befor: Int, count: Int) {
        btnSelected?.set(Util.isEmailValid(email?.get()!!) && s.toString().length >= 8)


    }

    fun login() {
        progressDialog?.value = true
        WebServiceClient.client.create(BackEndApi::class.java).LOGIN(email = email?.get()!!
                , password = password?.get()!!)
                .enqueue(this)

    }

    override fun onResponse(call: Call<User>?, response: Response<User>?) {
        progressDialog?.value = false
        userLogin?.value = response?.body()

    }

    override fun onFailure(call: Call<User>?, t: Throwable?) {
        progressDialog?.value = false

    }

}

where is the xml

<layout xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="viewmodel"
            type="mvvm.f4wzy.com.samplelogin.ui.login.viewmodel.LoginViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mainLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary">


        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:src="@mipmap/ic_progress"
            app:layout_constraintBottom_toTopOf="@+id/bottomLayout"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <LinearLayout
            android:id="@+id/bottomLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/ic_login_bottom_bg"
            android:orientation="vertical"
            android:paddingEnd="25dp"
            android:paddingStart="25dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="36dp"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_vertical"
                    android:src="@drawable/ic_person" />

                <android.support.design.widget.TextInputLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="17dp"
                    android:hint="@string/email">

                    <android.support.v7.widget.AppCompatEditText
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:imeOptions="actionNext"
                        android:inputType="text"
                        android:onTextChanged="@{viewmodel::onEmailChanged}"
                        android:paddingStart="5dp"
                        android:text="@={viewmodel.email}"
                        android:textColor="#383838"
                        android:textSize="20sp"
                        app:backgroundTint="#00ce8f" />

                </android.support.design.widget.TextInputLayout>
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_vertical"
                    android:src="@drawable/ic_lock" />

                <android.support.design.widget.TextInputLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="17dp"
                    android:hint="@string/password">

                    <android.support.v7.widget.AppCompatEditText
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:imeOptions="actionDone"
                        android:inputType="textPassword"
                        android:onTextChanged="@{viewmodel::onPasswordChanged}"
                        android:paddingStart="5dp"
                        android:text="@={viewmodel.password}"
                        android:textColor="#383838"
                        android:textSize="20sp"
                        app:backgroundTint="#00ce8f" />

                </android.support.design.widget.TextInputLayout>
            </LinearLayout>


            <Button
                android:id="@+id/btn_continue"
                style="@style/ButtonStyle"
                android:layout_width="152dp"
                android:layout_height="32dp"
                android:layout_gravity="center_horizontal"
                android:layout_marginBottom="24dp"
                android:layout_marginTop="24dp"
                android:enabled="@{viewmodel.btnSelected}"
                android:onClick="@{()->viewmodel.login()}"
                android:text="@string/login" />


        </LinearLayout>
    </android.support.constraint.ConstraintLayout>
</layout>

Note: You’ll find all the code for this project in the following Github repo.