Skip to main content

ViewModel with Jetpack Compose

 Compose uses remember API to store object in memory. Stored value is returned during recomposition . remember helps us retain data across recompostion , but when configuration changes happen all stored values are lost . One way to overcome this is to use rememberSaveable . rememberSaveable saves any value that can be saved in a Bundle , so it will survive configuration changes. But when we are using lot of data , for example a list we can cannot use a rememberSavble beacuse there is limit on amount of data that can be stored in Bundle . So we use ViweModel.

ViewModel provide the ui state and access to the business logic located in other layers of the app. It also survives configuration changes. ViewModel handles events coming from the UI or other layers of the app and updates the state holder based on the events.

We need to add the following dependency in our app level build.gradle to use ViewModel

implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1"
First create a ViewModel class , then we can access the ViewModel from the composable using viewModel() function.
class DemoViewModel : ViewModel() { ......}

@Composable
fun DemoFun1(
    viewModel: DemoViewModel = viewModel()
    //cereates a new ViewModel
) {
   ....}
@Composable
fun DemoFun2(
    viewModel: DemoViewModel = viewModel()
    //returns same instance as in DemoFun1
) {
   ....}
We should  call viewModel() function from screen level composable , that is close to root composable called from activity or fragment . We should never pass down ViewModel instances to other Composables , pass only data and fuctions. viewModel() creates a new ViewModel or returns a existing ViewModel in the given scope. ViewModel is retained as long as the scope is alive.
This is a demo application which shows how to use ViewModel with Jetpack Compose . Screenshots of the app  




This is our MainActivity
package com.arun.androidtutsforu.democomposeviewmodel

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.arun.androidtutsforu.democomposeviewmodel.ui.theme.DemoComposeViewModelTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            DemoComposeViewModelTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    CalculateScreen()
                }
            }
        }
    }
}
@Composable
fun CalculateScreen(composeViewModel: ComposeViewModel = viewModel()){
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "JetPack Compose Demo",
            fontWeight = FontWeight.Bold,
            fontSize = 20.sp,
            color = Color.Blue
        )
        Spacer(modifier = Modifier.height(32.dp))
        TextField(
            value =composeViewModel.firstNum,
            onValueChange ={composeViewModel.changeFirstNum(it)} ,
            label = { Text(text = "Enter First Num")}
        )
        Spacer(modifier = Modifier.height(16.dp))
        TextField(
            value = composeViewModel.secondNum,
            onValueChange = {composeViewModel.changeSecondNum(it)},
            label = { Text(text = "Enter Second Num")}
        )
        Spacer(modifier = Modifier.height(16.dp))
        Text(
            text = "The sum is ${composeViewModel.sum}",
            fontWeight = FontWeight.Bold,
            fontSize = 20.sp
        )
        Button(onClick = { composeViewModel.valuculate() }) {
            Text(text = "Calculate")
        }
    }
}
This is our ViewModel , ComposeViewModel
package com.arun.androidtutsforu.democomposeviewmodel
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class ComposeViewModel : ViewModel() {
    private val _firstNum = mutableStateOf("")
    val firstNum :String
    get() = _firstNum.value
    private val _secondNum = mutableStateOf("")
    val secondNum :String
        get() = _secondNum.value
    private var _sum = mutableStateOf(0)
    val sum:Int
      get() =_sum.value

    fun changeFirstNum(number: String){
        _firstNum.value = number
    }
    fun changeSecondNum(number: String){
        _secondNum.value = number
    }
   fun valuculate() {
       val num1 = firstNum.toIntOrNull()?:0
       val num2 = secondNum.toIntOrNull()?:0
       _sum.value = num1 + num2
   }
}
Viewmodel provides all our UI related data.
    private val _firstNum = mutableStateOf("")
    val firstNum :String
     get() = _firstNum.value
    private val _secondNum = mutableStateOf("")
    val secondNum :String
     get() = _secondNum.value
    private var _sum = mutableStateOf(0)
    val sum:Int
      get() =_sum.value
Create a private _firstNum variable using mutableStateOf , and expose only firstNum as a String so it's not modified outside the ViewModel.
We have done the same with other variables. Only our ComposeViewModel can change the values of these varibles. We can only get the values of these varible from the MainActivity. We can not modify it from MainActivity.
...
onValueChange ={composeViewModel.changeFirstNum(it)} 
...
onValueChange = {composeViewModel.changeSecondNum(it)}
...
text = "The sum is ${composeViewModel.sum}"
...
Button(onClick = { composeViewModel.valuculate() })

This is how our MainActivity gets value from our ViewModel. 

You can download full source code of this demo app from github

Comments

Popular posts

Android Sqlite and ListView Example

This is simple application which insert data into Sqlite database --and shows the data  from the database in a ListView ListView is not used in android Anymore . We use RecyclerView and CardView   in Android RecyclerView Demo is available on the following link http://androidtuts4u.blogspot.in/2017/04/android-recyclerview-example.html RecyclerView with CardView Demo is available on the following link http://androidtuts4u.blogspot.in/2017/09/android-card-view-example.html This  is the first Activity of application which shows data from database in listview. Register here buton will start a Registration Activity. Submit button will add data to database and show it in the ListView of MainActivity. Update can be performed by clicking ListView items.     you can download the source code of this project from  google drive   https://drive.google.com/folderview?id=0BySLpWhqmbbdS0dtT1R2TXdBWEE&usp=sharing click on the abov...

Android List View using Custom Adapter and SQLite

following is a simple applicaton to create ListView using  Custom adapter.screenshot of the application  is like this . ListView is not used in android Anymore . We use  RecyclerView  and  CardView   in Android RecyclerView Demo is available on the following link http://androidtuts4u.blogspot.in/2017/04/android-recyclerview-example.html RecyclerView with Cardview Example is available on the following link http://androidtuts4u.blogspot.in/2017/09/android-card-view-example.html The ListView below the submit button is populated using Custom Adapter.Data is stored and retrieved using SQLite databsase. you can download the source code of this project from  google drive   https://drive.google.com/folderview?id=0BySLpWhqmbbdUXE5aTNhazludjQ&usp=sharing click on the above link ->sign into  your  google account ->add this to your google drive -> open it in google drive and download it. To create a simple ...

Controlling Back button Behavior in Fragment-- kotlin

when user navigates in an application android maintains back stack of destinations . This allows android to go back to previous destination when user clicks back button . some  times we  need to implement our own back button behavior  for best user experience . For example  if we are in a timer based quiz  application . when the user clicks on the back button if we go back to the previous destination without stopping  the counter , it will crash  the application . So first we need to know whether the  user  pressed the back button accidentally or not by  using a dialogue box . If the user want to go back to previous destination  we need to stop the counter and  then go back to the previous destination. We can control the back button hahavior byusing OnBackPressedDispatcher  . This controls how back button events are dispatched to one or more onBackPressedCallback objects . callbacks are added using addCallback methods. e...