Jetpack Compose

Oct 11 2022 · Kotlin 1.7.10, Android 13, Android Studio Chipmunk

Part 1: Jetpack Compose Basics

04. Add State to Composables

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 03. Use Basic Compose Building Blocks Next episode: 05. Decouple Composables

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 04. Add State to Composables

The student materials have been reviewed and are updated as of September 2022.

Transcript: 04. Add State to Composables

Demo

So far you’ve been getting to know the Jetpack Compose framework, and it’s been going great! :]

You’ve learned lots about different types of components and composable functions, but you haven’t really handled or introduced any state to the UI.

Jetpack Compose has a really unique way of handling state, so let’s see how to implement it!

Open the AddBookActivity if you haven’t already. Add the following property at the top of the class:

private val _genresState = MutableLiveData(emptyList<Genre>())

You’ll use this state to store the genres that you can show in the dropdown menu! Now change the onCreate() function to load the genres as the screen is created:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContent { AddBookContent() }

  loadGenres()
}

private fun loadGenres() {
  lifecycleScope.launch {
    _genresState.value = repository.getGenres()
  }
}

This is important, as you need to have the genres ready to display them in a dropdown! Notice how you’re using coroutines here, as the repository is powered by suspend functions.

Next, add the following code to the the AddBookFormContent component:

val genres = _genresState.value ?: emptyList()
val isGenresPickerOpen = remember { mutableStateOf(false) }

These two properties will help you keep track of the genres you’re showing and the picker open state. Now add the rest of the properties.

val bookNameState = remember { mutableStateOf("") }
val bookDescriptionState = remember { mutableStateOf("") }
val selectedGenreName =
  genres.firstOrNull { it.id == _addBookState.value?.genreId }?.name ?: "None"

There’s quite a few properties here, so let’s go over them one by one. You fetch the genres from the LiveData value, and also get the currently selected genre name, to display in the dropdown.

The other three values are using something called the remember wrapper. Compose doesn’t remember state in its components like regular Views. If you want to store a piece of information in Compose, you have to tell it to remember the state.

Additionally, if you want the state to be changed, you need to use the mutableStateOf() function, which is similar to a LiveData object.

[Slide 1 - State in compose]

So what previously happened with your input fields is that you were typing in them, but you weren’t updating their state. Because of this, there was no input change within the field.

[Slide 2 - Compose Drawing]

And compose always draws only by the declarative definition you give it. So if you don’t give it a way to update its data, it’s not going to change.

[Slides 3 & 4 - Function of state]

So when you tie in the mutable state to compose, it knows how to update, as it’s always drawing the UI based on the state.

This is why it’s also said that in Compose the UI is a function of state.

[Slide 5 & 6 - Recomposition]

But what happens when you change the state? In compose, something called a Recomposition triggers.

A recomposition is the process of calling your functions with new state, and re-drawing the UI, based on the said state. Recomposition is really smart, because it skips all the composable functions and lambdas that weren’t impacted by the state change, meaning it can re-compose efficiently, without having to re-draw everything.

[Switch back to code]

Now that you understand recomposition a bit better, you can proceed to fill in the rest of the state handling.

Add the following properties to the Input fields:

OutlinedTextField(
value = bookNameState.value, // new
onValueChange = { newValue -> // new
  bookNameState.value = newValue 
  _addBookState.value = _addBookState.value?.copy(name = newValue)
},
label = { Text(text = stringResource(id = R.string.book_title_hint)) })

OutlinedTextField(
value = bookDescriptionState.value, // new
onValueChange = { newValue -> // new
  bookDescriptionState.value = newValue
  _addBookState.value =_addBookState.value?.copy(description = newValue)
},
label = { Text(text = stringResource(id = R.string.book_description_hint)) })

Here you defined that the input fields will change their state according to the bookNameState.value, and the description value. Any time those properties change, you’ll get a new data emission, and the component will recompose. You also update the addBookState to store the values for when you decide to add the book to your library.

Finally, wrap up the UI by adding the following handles for the DropdownMenu and the TextButton which toggles it:

Row {
  TextButton(
    onClick = { isGenresPickerOpen.value = true }, // new
    content = { Text(text = stringResource(id =  R.string.genre_select)) })

  DropdownMenu(
    expanded = isGenresPickerOpen.value, // new
    onDismissRequest = { isGenresPickerOpen.value = false }) { // new

    for (genre in genres) { // new
      DropdownMenuItem(onClick = {
        _addBookState.value = _addBookState.value?.copy(genreId = genre.id)
        isGenresPickerOpen.value = false
      }) {
        Text(text = genre.name)
      }
    }
  }
}

For the most part, you’re handling the way the dropdown is opened or closed within TextButton(). By adding appropriate handles, you don’t have to worry about the dropdown staying open when you select an item, or when you dismiss the window.

And in the dropdownContent part, you’re using a for loop to call the DropdownMenuItem function for each genre.

You might think you’re not actually creating the items here, but just by calling the functions you’re adding them to the Compose tree, so everything will work out!

You also hide the dropdown when you select an item, and update the addBookState.

Now build & run the app, and check the UI!

[Build & run]

Once you start writing in the input fields, you’ll see that the UI updates accordingly, and it’s seamless, even though the inputs are being re-composed! You can also pick a genre!

But wait, you’re not showing the picked genre to the user. Let’s change that.

Change the Row to add a Text, like so:

Row(verticalAlignment = Alignment.CenterVertically) { // alignment
  ...

  Text(text = selectedGenreName) // text element
}

By adding Text to the Row, a horizontal linear component, you can have the button for the select genre option, and a text to represent the currently selected genre.

Additionally, you added Alignment.CenterVertically, to keep both of the items aligned. Now build & run the app again, and everything should be fine, and you should be able to add books to your library!

[Build & Run, add book]

Awesome! You can see how easy to use and awesome Compose is!

In the next few episodes you’ll see how to make your components reusable, and clean up their design to be more material! See you there! :]