In this android example tutorial, we will see What is ViewModel and how to implement viewmodel pattern in Android Login Screen Kotlin code.
What is ViewModel?
In Android, the ViewModel class is intended to store and handle UI-related data in a lifecycle-aware manner. The ViewModel class enables data to persist via configuration changes such as screen rotations.
ViewModel Implementation in Login Screen:
Step 1: Create a new Project in android studio.
Go to File > New > New Project > Empty Activity > Next > Enter Name > Select Language Kotlin > Finish
|
Step 2: Open build.gradle(app) file and add the following code
Add ViewModel and LiveData Lifecycle dependency inside dependencies..
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
|
Add the kotlin-kapt inside plugins
Enable the DataBinding inside android...
buildFeatures{
dataBinding true
}
|
Step 3: Create LoginUser Model
Create a new kotlin file (LoginUser.kt) and add the following code:
class User(private var email: String, private var password: String) : BaseObservable() {
fun isDataValid(): Int {
if (TextUtils.isEmpty(getEmail()))
return 0
else if (!Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches())
return 1
else if (getPassword().length < 6)
return 2
else
return -1
}
fun getPassword(): String {
return password
}
fun getEmail(): String {
return email
}
fun setEmail(email: String) {
this.email = email
}
fun setPassword(password: String) {
this.password = password
}
}
|
Step 4: Create a Interface
Create a new kotlin file (LoginResultCallBacks.kt) which has two function onSuccess and onError be like:
interface LoginResultCallBacks {
fun onSuccess(message: String)
fun onError(message: String)
}
|
Step 5: Implement ViewModel
Create a new kotlin file (LoginViewModel.kt) and add the following code:
class LoginViewModel(private val listener: LoginResultCallBacks) : ViewModel() {
private val user: LoginUser
init {
this.user = LoginUser("", "")
}
val emailTextWatcher: TextWatcher
get() = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
user.setEmail(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Code what you want to show before edit the email box
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Code what you want to show on text Changed in email box
}
}
//create function to set Password after user finish enter text
val passwordTextWatcher: TextWatcher
get() = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
user.setPassword(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Code what you want to show before edit the password box
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Code what you want to show on text Changed in password box
}
}
fun onLoginClicked(v: View) {
val loginCode: Int = user.isDataValid()
if (loginCode == 0)
listener.onError("Enter Email ID")
else if (loginCode == 1)
listener.onError("Invalid Email")
else if (loginCode == 2)
listener.onError("Password must be greater than 5")
else
listener.onSuccess("Success")
}
}
|
TextWatcher: The Android Developer API has a useful class called TextWatcher. It can be used to keep an eye on an input text field and rapidly update data in other views. It can be handy for immediately counting the number of characters input in the text field and measuring password strength when entering, among other things.
Methods of TextWatcher
abstract void
|
afterTextChanged(Editable s)
This method is called to notify you that, somewhere within s, the text has been changed.
|
abstract void
|
beforeTextChanged(CharSequence s, int start, int count, int after)
This method is called to notify you that, within s, the count characters beginning at start are about to be replaced by new text with length after.
|
abstract void
|
onTextChanged(CharSequence s, int start, int before, int count)
This method is called to notify you that, within s, the count characters beginning at start have just replaced old text that had length before.
|
Create a new file of Kotlin (LoginViewModelFactory.kt) and add the following code:
We need to pass some input data to the constructor of the viewModel , we need to create a factory class for viewModel.
class LoginViewModelFactory (private val listener: LoginResultCallBacks):ViewModelProvider.NewInstanceFactory() {
override fun create(modelClass: Class): T {
return LoginViewModel(listener) as T
}
}
|
Step 6: Open activity_main.xml file and add the following xml code.
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.nishajain.loginregisterwithviewmodel.LoginViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="Login"
android:textSize="29sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Email"
app:addTextChangedListener="@{viewModel.emailTextWatcher}"></EditText>
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Enter Password"
app:addTextChangedListener="@{viewModel.passwordTextWatcher}"></EditText>
<Button
android:id="@+id/loginBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="@{viewModel::onLoginClicked}"
android:text="Login"></Button>
</LinearLayout>
</layout>
|
Above layout file gives you output as below:
Step 7: Go to MainActivity.kt file
Bind the view with activity, add the following code below setContentView(R.layout.activity_main).
val activityMainBinding = DataBindingUtil.setContentView activityMainBinding.viewModel = ViewModelProviders.of(this,LoginViewModelFactory(this)).get(LoginViewModel::class.java)
|
Implement the LoginResultCallBacks Interface in MainActivity.kt file
class MainActivity : AppCompatActivity(), LoginResultCallBacks {
|
Implement the methods of LoginResultCallBacks onSuccess() and onError()
override fun onSuccess(message: String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
|
Final Code of MainActivity.kt file,
class MainActivity : AppCompatActivity(), LoginResultCallBacks {
override fun onSuccess(message: String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
activityMainBinding.viewModel = ViewModelProviders.of(this, LoginViewModelFactory(this))
.get(LoginViewModel::class.java)
}
}
|
Step 8: Now run the app in your emulator or real device, you will get the given output:
When the field is empty:
When we typed wrong email address:
When we entered password value less than 6 digits:
When everthing is Okay then it shows you success message:
Complete Source Code of Login with ViewModel Example:
activity_main.xml file
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.nishajain.loginregisterwithviewmodel.LoginViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="Login"
android:textSize="29sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Email"
app:addTextChangedListener="@{viewModel.emailTextWatcher}"></EditText>
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Enter Password"
app:addTextChangedListener="@{viewModel.passwordTextWatcher}"></EditText>
<Button
android:id="@+id/loginBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="@{viewModel::onLoginClicked}"
android:text="Login"></Button>
</LinearLayout>
</layout>
|
MainActivity.kt file
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.nishajain.loginregisterwithviewmodel.databinding.ActivityMainBinding
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProviders
class MainActivity : AppCompatActivity(), LoginResultCallBacks {
override fun onSuccess(message: String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
activityMainBinding.viewModel = ViewModelProviders.of(this, LoginViewModelFactory(this))
.get(LoginViewModel::class.java)
}
}
|
LoginUser.kt file
import android.text.TextUtils
import android.util.Patterns
import androidx.databinding.BaseObservable
class LoginUser(private var email: String, private var password: String) : BaseObservable() {
fun isDataValid(): Int {
if (TextUtils.isEmpty(getEmail()))
return 0
else if (!Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches())
return 1
else if (getPassword().length < 6)
return 2
else
return -1
}
fun getPassword(): String {
return password
}
fun getEmail(): String {
return email
}
fun setEmail(email: String) {
this.email = email
}
fun setPassword(password: String) {
this.password = password
}
}
|
LoginResultCallBacks.kt file
interface LoginResultCallBacks {
fun onSuccess(message: String)
fun onError(message: String)
}
|
LoginViewModel.kt file
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import androidx.lifecycle.ViewModel
class LoginViewModel(private val listener: LoginResultCallBacks) : ViewModel() {
private val user: LoginUser
init {
this.user = LoginUser("", "")
}
val emailTextWatcher: TextWatcher
get() = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
user.setEmail(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Code what you want to show before edit the email box
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Code what you want to show on text Changed in email box
}
}
//create function to set Password after user finish enter text
val passwordTextWatcher: TextWatcher
get() = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
user.setPassword(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Code what you want to show before edit the password box
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Code what you want to show on text Changed in password box
}
}
fun onLoginClicked(v: View) {
val loginCode: Int = user.isDataValid()
if (loginCode == 0)
listener.onError("Enter Email ID")
else if (loginCode == 1)
listener.onError("Invalid Email")
else if (loginCode == 2)
listener.onError("Password must be greater than 5")
else
listener.onSuccess("Success")
}
}
|
LoginViewModelFactory.kt file
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class LoginViewModelFactory (private val listener: LoginResultCallBacks):ViewModelProvider.NewInstanceFactory() {
override fun create(modelClass: Class): T {
return LoginViewModel(listener) as T
}
}
|
build.gradle(app) file
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.nishajain.loginregisterwithviewmodel"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures{
dataBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
}
|
Conclusion: In this example we have covered what is Android Jetpack ViewModel and how to implement Login Screen with ViewModel in Android Studio using Kotlin Language.