Home Android & Kotlin Books Jetpack Compose by Tutorials

11
Reacting to Compose Lifecycle Written by Tino Balint

In previous chapters, you focused on building the JetReddit app by adding advanced layouts and complex UI.

In this chapter, you’ll learn how to react to the lifecycle of composable functions. This approach will allow you to execute your code at specific moments while your composable is active.

Jetpack Compose offers a list of events that can trigger at specific points in the the lifecycle, called effects. Throughout this chapter, you’ll learn about the different kinds of effects and how to use them to implement your logic.

Events in Compose

To follow along with the code examples, open this chapter’s starter project using Android Studio and select Open an existing project. Navigate to 11-reacting-to-compose-lifecycle/projects and select the starter folder as the project root. Once the project opens, let it build and sync and you’re ready to go!

You might already be familiar with the project hierarchy from the previous chapter, but in case you aren’t, look at the following image:

Project Hierarchy
Project Hierarchy

In this chapter, you’ll only work with two of these packages: screens, to implement a new screen, and routing, to add a new routing option. The rest of the packages are already prepared to handle navigation, fetching data from the database, dependency injection and theme switching for you.

Once you’re familiar with the file organization, build and run the app. You’ll see:

Home Screen
Home Screen

This is a fully implemented home screen. When you browse the app, you’ll notice that two screens are pre-built and implemented for you: My Profile, in the app drawer, and New Post, the third option in the bottom navigation.

In this chapter, you’ll implement the option to choose a community inside the New Post screen:

New Post Screen
New Post Screen

However, before you start building this new screen, you need to learn more about effects in Compose.

Basic effects in Compose

In Compose, an effect is an event that triggers at a specific time during the composable lifecycle. There are three basic events:

onActive { Log.d("HomeScreen", "onActive") }
onCommit { Log.d("HomeScreen", "onCommit") }
onDispose { Log.d("HomeScreen", "onDispose") }

Implementing the community chooser

Next, you’ll implement a community chooser like the one the original Reddit app uses. Look at the following image for reference:

Reddit Community Chooser
Xurkur Ropmotirk Claegug

Creating a list of communities

As you learned in the previous chapters, you’ll build the smaller components first, starting with SearchedCommunities(). Start by changing SearchedCommunities() code to the following:

@Composable
fun SearchedCommunities(
  communities: List<String>,
  viewModel: MainViewModel?,
  modifier: Modifier = Modifier
) {
  communities.forEach {
    Community(
      text = it,
      modifier = modifier,
      onCommunityClicked = {
        viewModel?.selectedCommunity?.postValue(it)
        JetRedditRouter.goBack()
      }
    )
  }
}
@Preview
@Composable
fun SearchedCommunitiesPreview() {
  Column {
    SearchedCommunities(defaultCommunities, null, Modifier)
  }
}
Searched Communities Preview
Leudyqah Fagbeguvuel Tfijiir

Making the community list searchable

The next step is to add a TextField() to search the communities according to user input. Replace ChooseCommunityScreen() with the code below:

@Composable
fun ChooseCommunityScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
  val scope = rememberCoroutineScope()
  val communities: List<String> by viewModel.subreddits.observeAsState(emptyList())
  var searchedText by remember { mutableStateOf("") }
  var currentJob by remember { mutableStateOf<Job?>(null) }

  onActive {
    viewModel.searchCommunities(searchedText)
  }

  Column {
    ChooseCommunityTopBar()
    TextField(
      value = searchedText,
      onValueChange = {
        searchedText = it
        currentJob?.cancel()
        currentJob = scope.async {
          delay(SEARCH_DELAY_MILLIS)
          viewModel.searchCommunities(searchedText)
        }
      },
      leadingIcon = { Icon(Icons.Default.Search) },
      label = { Text(stringResource(R.string.search)) },
      modifier = modifier
        .fillMaxWidth()
        .padding(horizontal = 8.dp),
      backgroundColor = MaterialTheme.colors.surface,
      activeColor = MaterialTheme.colors.onSurface
    )
    SearchedCommunities(communities, viewModel, modifier)
  }
}
Community Chooser
Jiftijegs Yziudop

Implementing the back button handler

In previous sections, you used built-in back button handlers. This time, you’ll use effects to build your own.

@Composable
fun BackButtonHandler(
  enabled: Boolean = true,
  onBackPressed: () -> Unit
) {
  val dispatcher = BackPressedDispatcher.current ?: return
  val backCallback = remember {
    object : OnBackPressedCallback(enabled) {
      override fun handleOnBackPressed() {
        onBackPressed.invoke()
      }
    }
  }
  DisposableEffect(dispatcher) {
    dispatcher.addCallback(backCallback)
    onDispose {
      backCallback.remove()
    }
  }
}

Adding an action to the back button

The next step is to build BackButtonAction() and provide the previous Ambient. Replace BackButtonAction() with the following:

@Composable
fun BackButtonAction(onBackPressed: () -> Unit) {
  Providers(
    BackPressedDispatcher provides (
        AmbientLifecycleOwner.current as ComponentActivity
        ).onBackPressedDispatcher
  ) {
    BackButtonHandler {
      onBackPressed.invoke()
    }
  }
}

Calling the back button’s action

Now that you’ve implemented BackButtonAction(), the only thing left to do is to call it from inside ChooseCommunityScreen().

BackButtonAction {
  JetRedditRouter.goBack()
}

Complex effects in Compose

To understand the topic of complex effects more clearly, you first need to learn how side effects work in Compose.

SideEffect

SideEffect() ensures that your event only executes when a composition is successful. If the composition fails, the event is discarded. In addition, only use it when you don’t need to dispose the event, but want it to run with every recomposition.

@Composable
fun MainScreen(router: Router) {
  val drawerState = rememberDrawerState(DrawerValue.Closed)

  SideEffect {
    router.isRoutingEnabled = drawerState.Closed
  }
}

LaunchedEffect

LaunchedEffect launches a coroutine into the composition’s CoroutineScope. Just like rememberCoroutineScope(), its coroutine is canceled when LaunchedEffect leaves the composition and will relaunch on recomposition.

@Composable
fun SpeakerList(searchText: String) {
  var communities by remember { mutableStateOf<List<String>>(emptyList()) }
  LaunchedEffect(searchText) { 
    communities = viewModel.searchCommunities(searchText)
  }

  Communities(communities)
}

Invalidate

invalidate() is an Effect that manually invalidates the composition, which causes recomposition.

@Composable
fun MyComposable(viewModel: ViewModel) {
    val name = viewModel.getName { invalidate() }
    Text(text = "Hello: $name")
}

Key points

  • onActive() triggers the event only once, upon the first composition.
  • onCommit() triggers an event every time composition occurs.
  • onDispose() triggers an event when your composable leaves the composition.
  • Use rememberCoroutineScope() when you are using coroutines and need to cancel and relaunch the coroutine after an event.
  • Use LaunchedEffect() when you are using coroutines and need to cancel and relaunch the coroutine every time your parameter changes and it isn’t stored in a mutable state.
  • DisposableEffect() is useful when you aren’t using coroutines and need to dispose and relaunch the event every time your parameter changes.
  • SideEffect() triggers an event only when the composition is successful and you don’t need to dispose the subject.
  • invalidate() manually triggers recomposition.

Where to go from here?

Congratulations! Now, you know how to react to Compose lifecycle, which is one of the most complex parts of Jetpack Compose. At this point, you’ve seen an overview of how to solve some of the most complex and important problems you encounter while working with Compose.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.