Android Jetpack Compose: Remember Made Easy
Tags: Android Development, Jetpack Compose, Android App Development, Android, AndroidDev

Learning Android Development

Android Jetpack Compose: Remember Made Easy

Make Learning Jetpack Compose Easier

Photo by Josh Riemer on Unsplash

We learn about Recomposition previously, where the State Variable is like a remote control. When the State Variable changes, it triggers only the particular UI that interacts with the State Variable to be redrawn (recompose).

There’s another concept important for Jetpack Compose, that’s Remember.

Recomposition recap

Before we understand Remember, let’s relook into Recomposition.

The State Variable is usually outside the composable function like below

class MainActivity : AppCompatActivity() {



var isOn by mutableStateOf(false)



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContent {

Box(modifier = Modifier.fillMaxSize().background(

if (isOn) Color.Green else Color.Red)

)

Button(

modifier = Modifier.padding(64.dp).fillMaxSize(),

onClick = { isOn = !isOn }) {

Text(

text = if (isOn) "On" else "Off",

color = if (isOn) Color.Green else Color.Red,

fontSize = 48.sp

)

}

}

}

}

Given the button and the box are all within a composable function, having the State Variable outside of it, looks kind of odd. How nice if we can encapsulate the State Variable within the composable function as below.

class MainActivity : AppCompatActivity() {



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContent {

var isOn by mutableStateOf(false) Box(modifier = Modifier.fillMaxSize().background(

if (isOn) Color.Green else Color.Red)

)

Button(

modifier = Modifier.padding(64.dp).fillMaxSize(),

onClick = { isOn = !isOn }) {

Text(

text = if (isOn) "On" else "Off",

color = if (isOn) Color.Green else Color.Red,

fontSize = 48.sp

)

}

}

}

}

But unfortunately, composable function is still a function. Every time the composable function get recompose, the isOn variable gets reset to false . This makes the UI always in the false state and the button seems not able to trigger the UI change at all.

Remember came to rescue

The Jetpack Compose team created the remember function as below, which we can use to store the State Variable within.

class MainActivity : AppCompatActivity() {



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContent {

var isOn by remember { mutableStateOf(false) } Box(modifier = Modifier.fillMaxSize().background(

if (isOn) Color.Green else Color.Red)

)

Button(

modifier = Modifier.padding(64.dp).fillMaxSize(),

onClick = { isOn = !isOn }) {

Text(

text = if (isOn) "On" else "Off",

color = if (isOn) Color.Green else Color.Red,

fontSize = 48.sp

)

}

}

}

}

Instead of creating a new one every time, whenever recompose, the remember will retrieve the previous State Variable, hence making it persist within the composable function, and not reconstructed every time the function recompose.

Remember with keys

The Remember function actually comes with a key that one can supply.

@Composablefun <T>

remember(vararg inputs: Any?, calculation: () -> T): T

Thanks Leland Richardson for his blog in sharing it.

Do note it is vararg , which means it can take any number of keys.

How it works in a simplified diagram as below.

From here we know that it only stores one entry. When a new key is provided, it will generate a new object. If one gets back to the previous key, it will generate another new object (i.e. it is NOT like map where we have a map of key-value.)

To illustrate this better, I create another simple app as below.

In this App, we have a switch that changes the color from Red-Green to Black-White (I name the boolean checking as isGrayScale ).

With the switch, I will change the color choice, using isGrayScale as the key. So depends on the value of isGrayScale , the ColorChoice will change.

val colorChoice by remember(isGrayScale) {

mutableStateOf(

if (isGrayScale)

ColorChoice(

onColor = Color.White,

offColor = Color.Black)

else

ColorChoice(

onColor = Color.Green,

offColor = Color.Red)

)

}

The ColorChoice is a simple class as below.

class ColorChoice(

private val onColor: Color,

private val offColor: Color

) {

var isOn = false

var color by mutableStateOf(offColor)

fun toggle() {

isOn = !isOn

color = if(isOn) { onColor } else { offColor }

}

}

Notice I made color as a State Variable, as I want the UI to change when the value is updated.

And lastly, all the drawing composable functions as below that change as colorChoice.color change. (i.e. when isGrayScale changes, the colorChoice.color also changes)

Box(modifier = Modifier

.fillMaxSize()

.background(colorChoice.color))

Button(

modifier = Modifier.padding(64.dp).fillMaxSize(),

onClick = colorChoice::toggle) {

Text(

text = if (colorChoice.isOn) "On" else "Off",

color = colorChoice.color,

fontSize = 48.sp

)

}

Switch(

modifier = Modifier.padding(64.dp).fillMaxWidth(),

checked = isGrayScale,

onCheckedChange = {isGrayScale = !isGrayScale}

)

Note: In the Button onClick I didn’t use { colorChoice.toggle() } , instead, I use colorChoice::toggle directly. The reason is, if we put it in the lambda, the first colorChoice is kept within the lambda and not change when the colorChoice has changed. Check this StackOverflow.

You can access the full code here.

When should we use “remember”?

One thing that wasn’t mentioned above is, the store object in remember doesn’t survive state restoration. If the App is killed by the system, the value is gone.

My personal recommendation is to consider supply the State Variable externally, e.g. from the ViewModel, etc.

First, you have a better separation of business logic and UI logic.

Second, you can handle state restoration there better

But if the State is internal to your UI, e.g. animation value, and state restoration is not crucial, then use remember.