Understanding State in Android Jetpack Compose (with Examples)

Jetpack Compose State

Almost every app updates the UI on the run time. But, how do we do this in Jetpack Compose? By managing the state. Today, we will learn how to handle the state and update the UI in Android Jetpack Compose.

Prerequisites:

What is a State?

State in an app is any value that can change over time. It decides what should be displayed in the UI.

For this article, we will implement the following button.

Button Click Count

When you tap on the button, the text is getting changed. In other words, the state is getting changed (or updated). Let’s make this UI.

First, create an empty Jetpack Compose project. Next, open the MainActivity.kt file and create a composable with MyUI() name outside the class.

@Composable
fun MyUI() {

}

Remove the Greeting() and DefaultPreview() composables. In the onCreate() method, delete the Surface() composable and call MyUI(). Our final MainActivity class looks like this.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourProjectNameTheme {
               MyUI()
            }
        }
    }
}

@Composable
fun MyUI() {
    
}

Create a button in the MyUI().

@Composable
fun MyUI() {

    Button(
        onClick = {  },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        )
    ) {
        Text(text = "")
    }
}

Run it. It shows a blank button with a yellow background.

Blank Button

We have to show the number of clicks. So, Create a count variable and increment it in the onClick block. Display its value with the Text() composable.

@Composable
fun MyUI() {

    var count = 0

    Button(
        onClick = { count++  },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        )
    ) {
        Text(text = "Count $count")
    }
}

Run the project. If you tap on the button, the count value will never increase.

Button Click Not Working Jetpack Compose

To understand what is going on, log the count value before the Button() and inside the onClick block.

@Composable
fun MyUI() {

    var count = 0

    Log.d("Before Button()", "Count = $count")

    Button(
        onClick = {
            count++
            Log.d("Inside onClick", "Count = $count")
        },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        )
    ) {
        Text(text = "Count $count")
    }
}

If you run the app, it displays Before Button(): Count = 0 first, and every time you click on the button, the count will be incremented.

Our UI is not getting updated even if the count value is increased. This is because we are using normal Kotlin variables. We have to use the objects of type MutableState. It holds a value and whenever the value is changed, it updates the UI (corresponding composable).

In Jetpack Compose, we can create a MutableState object in the following way.

var count by remember { mutableStateOf(0) }

Write it at the beginning of the MyUI(). You may have to import the packages multiple times.

@Composable
fun MyUI() {

    var count by remember { mutableStateOf(0) }

    Log.d("Before Button()", "Count = $count")

    Button(
        onClick = {
            count++
            Log.d("Inside onClick", "Count = $count")
        },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        )
    ) {
        Text(text = "Count $count")
    }
}

Run the app. You will get the expected output. Let us understand the code.

mutableStateOf():

It is a function that accepts a value and creates a new MutableState object. It initializes the object with the passed value and returns it.

remember:

It is a composable function. It helps us to store a single object in memory. A value computed by remember is stored during the initial composition, and the stored value is returned whenever we update the UI.

For example, when you first call the MyUI(), 0 is stored in the memory because 0 is the default value we sent to mutableStateOf() method. When you click on the button, 0 is returned and 1 is added to it. The result is stored in the memory. When you tap on the button second time, 1 is returned and 1 is added to it. The process gets repeated again and again. The old values get replaced by the new values.

remember can be used to store both mutable and immutable objects.

by Delegate:

In simple words, it converts the MutableState object into a regular Kotlin variable.

In the above code, mutableStateOf() returns MutableState object. The by Delegate changes it to Int. Place the mouse over the count variable. You will see its type.

mutableStateOf Int

If you don’t want to use by Delegate, you can also use =.

var count = remember { mutableStateOf(0) }

But, you have to use count.value to update or get the count value.

@Composable
fun MyUI() {

    var count = remember { mutableStateOf(0) }

    Log.d("Before Button()", "Count = ${count.value}")

    Button(
        onClick = {
            count.value++
            Log.d("Inside onClick", "Count = ${count.value}")
        },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        )
    ) {
        Text(text = "Count ${count.value}")
    }
}

mutableStateOf() accepts other data types also.

// To store string
var string1 by remember {
    mutableStateOf("Some string")
}

// To store a float value
var float1 by remember {
    mutableStateOf(0f)
}

ViewModel with Jetpack Compose:

You don’t have to use MutableState to save the state in Jetpack Compose. We can also use LiveData. But, we should convert it to State using observeAsState() method to update the UI.

First, Open this page and search for ViewModel and LiveData related dependencies. Add them to your gradle files.

Next, create a class and name it MyViewModel. It should extend ViewModel().

class MyViewModel : ViewModel() {

}

Next, declare a count variable with type MutableLiveData. Create an increment() function and increase the count value by 1 in it.

class MyViewModel : ViewModel() {

    var count = MutableLiveData<Int>(0)

    fun increment() {
        count.value = count.value?.plus(1)
    }
}

In the MyUI() method, add a parameter of type MyViewModel.

@Composable
fun MyUI(myViewModel: MyViewModel = viewModel()) {

}

Add the following line at the beginning.

val clickCount by myViewModel.count.observeAsState(0)

Here, myViewModel.count returns the count variable (which is of type MutableLiveData). observeAsState() converts it to State object. The by delegate changes the State object to a normal Koltlin variable. We are assigning it to clickCount.

In the onClick block, call myViewModel.increment() method so that our clickCount will be increased by 1.

@Composable
fun MyUI(myViewModel: MyViewModel = viewModel()) {

    val clickCount by myViewModel.count.observeAsState(0)

    Button(
        onClick = {
            myViewModel.increment()
        },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        )
    ) {
        Text(text = "Count $clickCount")
    }
}

Run the project. The clickCount will be incremented every time you tap on the button.

Recomposition:

This is a common word you encounter when working with Jetpack Compose. Recomposition is the process of updating the UI when the corresponding state changes. For example, in the above code, the Button() gets redrawn on the screen when the clickCount changes.

Only the composables that depend on the state will be redrawn. Look at the following code.

@Composable
fun MyComposable(text1: String, text2: String) {

    // This will recompose (re-drawn) when [text1] changes,
    // but not when [text2] changes
    Text(text = text1)

    // This will recompose when [text2] changes,
    // but not when [text1] changes
    Text(text = text2)
}

Leave a Comment