Lifecycle of Composables in Jetpack Compose

Learn about the lifecycle of a composable function and also find out how to use recomposition to build reactive composables. By Lena Stepanova.

3 (2) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Observing Logs and Conditions

Open Logcat, select com.yourcompany.android.quizme and observe the recomposition in the log output:

D/MainLog: Checked state true
D/MainLog: Button recomposed
D/MainLog: Checked state false
D/MainLog: Button recomposed

As you can see, the recomposition happened as many times as you clicked on the checkbox.

Now add logic for disabling the button if the checkbox isn’t checked. In QuizScreen(), add a condition for SubmitButton():

if (checked) {
   SubmitButton(checked, stringResource(id = R.string.try_me)) { }
}

Now Compose won’t do the button recomposition if the box isn’t checked.

Build and run the app. Tap the checkbox a few times. Notice the button is visible only when you select the checkbox.

Recomposed button

The log output is different as well:

D/MainLog: Checked state true
D/MainLog: Button recomposed
D/MainLog: Checked state false
D/MainLog: Checked state true
D/MainLog: Button recomposed
D/MainLog: Checked state false

Recomposition follows conditional rules, just like every other piece of code in Kotlin. When the checked state is set to false, there’s no need for Jetpack Compose to recompose the SubmitButton().

Skipping Recomposition

Jetpack Compose is intelligent and can choose to skip recomposition when it’s not needed.

Inside the if clause, above SubmitButton(), assign a new value to the questions variable:

questions = listOf(
  "What's the best programming language?",
  "What's the best OS?",
  "The answer to Life, The Universe and Everything?"
)

Here you added one more question to the list. This will result in adding a new input field dynamically the first time the user selects the checkbox.

Build and run the app. Select the checkbox and you’ll see a new text field appears:

Selecting checkbox adds new text field

Check the log output. You’ll see the following:

D/MainLog: Checked state true
D/MainLog: Button recomposed
D/MainLog: QuizInput The answer to Life, The Universe and Everything?

Notice that the logs contain only the new QuizInput field name here. Jetpack Compose recognized that the first two items in the list hadn’t changed, so it didn’t have to recompose them.

When you select the checkbox, it sends an event to the QuizScreen(), which sets the checked state to the new value. In its turn, Jetpack Compose knows which composables to recompose. In the log output, you see that the CheckBox() gets recomposed each time the user selects or unselects it. But the button gets recomposed only when the checked state is true. InputFields() isn’t affected by the user interaction with the checkbox at all.

This happens because Jetpack Compose skips recomposition where possible to stay optimized. Another example of this is when a conditional statement defines when not to show a composable as with SubmitButton() or when the state doesn’t affect a composable or the state hasn’t changed, as with InputFields().

Smart Recomposition

How does Jetpack Compose know whether the state has changed? It simply uses equals() to compare the new and the old values of the mutable state. Therefore, any immutable type won’t trigger recomposition.

Do you remember trying to use InputFields() with the immutable String value at the beginning of the tutorial? Jetpack Compose didn’t want to recompose anything because the value couldn’t change.

Any immutable type is stable and doesn’t trigger recomposition. That’s why you use remember() combined with mutableStateOf(). Jetpack Compose observes changes of the mutable state so it knows when to start recomposition and which composable functions it should recompose.

Modify the previous code block like this:

questions = listOf(
   "The answer to Life, The Universe and Everything?",
   "What's the best programming language?",
   "What's the best OS?"
) 

Here you shuffled questions in the list.

Build and run the app again. Select the checkbox.

Recomposed three questions

At first sight, it still looks the same. But in the log output, you’ll see Compose recomposed all three inputs:

D/MainLog: Checked state true
D/MainLog: Button recomposed
D/MainLog: QuizInput The answer to Life, The Universe and Everything?
D/MainLog: QuizInput What's the best programming language?
D/MainLog: QuizInput What's the best OS?

Once Jetpack Compose started to compare the new list with the old, it noticed that the first item didn’t equal the first item of the old list. In that case, it couldn’t reuse the old QuizInputs() and had to recompose them all. But how to be sure Jetpack Compose will use old composables if there are any?

Modify QuizInputFields() by wrapping QuizInput() into key():

key(question){
    QuizInput(question = question)
}

To ensure Jetpack Compose reuses old composables, you use key(). The question value you provided is identifier for a certain instance of QuizInput.

Build and run the app again. Select the checkbox. The UI remains the same!

Recomposed three questions

Recheck the log output. This time, it looks like when you added the new question to the bottom of the list – only the new InputField() was recomposed:

D/MainLog: Checked state true
D/MainLog: Button recomposed
D/MainLog: QuizInput The answer to Life, The Universe and Everything?

Although the list order changed, Jetpack Compose reused two initial question fields because it could identify them this time. This is an important concept to remember when you need to render longer lists or when the list order is essential.

Interacting With ViewModels

What you want to do now is to verify the user inputs and let them know if they’re on the right track. Check out the repository and business folders in your project.

To produce questions and validate the answers, you’ll mock the backend in QuestionsRepository. You’ll use QuizViewModel to connect the UI with your data source.

Passing Data From a ViewModel to a Composable

Add these two functions to QuizViewModel to fetch questions:

fun fetchQuestions(): List<String> {
   return repository.getQuestions()
}

fun fetchExtendedQuestions(): List<String>  { 
   return repository.getExtendedQuestions()
}

These will call the functions from the repository to fetch a basic and an extended list of questions, which you introduced earlier.

In QuizScreen.kt, find QuizScreen() and replace the hardcoded listOf() questions with the function call from quizViewModel:

var questions by remember {
   mutableStateOf(quizViewModel.fetchQuestions())
}

Also, above SubmitButton(), modify the extended list by replacing listOf() with this:

questions = quizViewModel.fetchExtendedQuestions()

Build and run the app. You shouldn’t notice any changes because recomposition still works as before. Only the data source has changed.

Recomposed three questions

Congrats! You just connected QuizViewModel with your composable function.