Simple Android MVVM using Android Architecture component and Kotlin
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.
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>