Jetpack Compose: Managing state of UI

26 / Mar / 2024 by Reyansh Jatoliya 0 comments

In Jetpack compose state determine what is shown on UI. It is a value that changes over time. There are 2 ways to maintain state.

Stateless composable: Stateless composable does not have any state or rely on parent state. This kind of composable does not require re- composition of UI. It is used to display static UI.

Stateful composable: These kind of composable maintain their own state. it defines the internal state & controls it. Jetpack compose has a reactive approach for storing state in a variable, we need to use MutableStateOf or observable like LiveData, Flow, etc. for the same. When the state change, the composable recompose itself, update the new ui with the new state.

Let us understand state using counter app, which increase the count upon button click.

Let us create a method IncreaseCounter which is hard coded with 0

ComposeStateDemoTheme {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        IncreaseCounter(0)
    }
}
@Composable
fun IncreaseCounter(count: Int) {
    var currentCount = 0;
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            "Count is : $currentCount",
            style = MaterialTheme.typography.bodyLarge,
            modifier = Modifier.padding(5.dp)
        )
        Button(
            onClick = {
                currentCount = count + 1
            },

            contentPadding = PaddingValues(16.dp),
            colors = ButtonDefaults.textButtonColors(
                containerColor = Color.LightGray,
                contentColor = Color.Black
            )
        ) {
            Text("Counter Increment")
        }
    }

}

When we run the app and click on increment button, notice that nothing is happening, that is because compose does not redraw when the state change until recomposes of composable method.

This can be achieved using:

MutableStateOf: To indicate to compose that should track an object’ state, the state needs to be of type State(immutable) or MutableState (mutable).

ComposeStateDemoTheme {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val count = mutableStateOf(0)
        IncreaseCounter(count)
    }
}

Above as you can see, MutableStateOf does the work but it is not the best way, because in jetpack compose every composable function recomposes independently so it can give unexpected if we use the same variable with multiple composable methods.

remember API: To instruct Compose to retain and reuse its value during recompositions, you need to declare it with the remember API. It is used as a guard against the recomposition to avoid the state being reset.

ComposeStateDemoTheme {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val count = remember {
            mutableStateOf(0)
        }
        IncreaseCounter(count)
    }
}

The above code won’t work on rotation of device i.e configuration changes etc. this can be avoided by using rememberSavable API.

rememberSavable API: It is used to save values that you need if Android OS destroys and recreates the activity.

ComposeStateDemoTheme {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            val count by rememberSaveable { mutableStateOf(0) }
            IncreaseCounter(count)
        }
    }
}

Final Code

ComposeStateDemoTheme {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        var count by rememberSaveable { mutableStateOf(0) }
        IncreaseCounter(currentCount = count) {
            count = it + 1;
        }
    }
}
@Composable
fun IncreaseCounter(currentCount: Int, updateCount: (Int) -> Unit) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            "Count is : ${currentCount}",
            style = MaterialTheme.typography.bodyLarge,
            modifier = Modifier.padding(5.dp)
        )
        Button(
            onClick = {
                updateCount(currentCount)
            },

            contentPadding = PaddingValues(16.dp),
            colors = ButtonDefaults.textButtonColors(
                containerColor = Color.LightGray,
                contentColor = Color.Black
            )
        ) {
            Text("Counter Increment")
        }
    }

}

Keep Learning! Keep Coding!
Follow us for more insights.

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *