https://kotlinlang.org logo
#compose
Title
# compose
b

Billy Newman

11/03/2023, 1:53 PM
Hello all, I am trying to track down an issue with multiple recomposes when trying to implement a custom TopBar for my app. Code in thread
Application TopBar with option nav icon and callback
Copy code
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBar(
   title: String,
   navigationIcon: ImageVector? = null,
   onNavigationClicked: (() -> Unit)? = null,
) {
   TopAppBar(
      title = {
         Text(
            text = title,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            style = MaterialTheme.typography.titleMedium
         )
      },
      navigationIcon = {
         navigationIcon?.let { icon ->
            IconButton(onClick = { onNavigationClicked?.invoke() } ) {
               Icon(icon, contentDescription = "Navigation")
            }
         }
      }
   )
}
Compose screen with only TopBar
Copy code
@Composable
fun Screen(
   onBack: () -> Unit,
) {
   Log.i("Compose", "screen compose")

   Column {
      TopBar(
         title = "Screen",
         navigationIcon = Icons.Default.ArrowBack,
         onNavigationClicked = { onBack() }
      )
   }
}
With this I see the compose happen 3 times. However if I remove the option onNavigationClicked parameter its only composed once.
a

ascii

11/03/2023, 2:31 PM
onBack
may not be memoized. Where does that function come from? Try wrapping it in
rememberUpdatedState
or simply
remember(keys used in block) { block }
b

Billy Newman

11/03/2023, 2:33 PM
Comes from the navigation composable
Copy code
composable("myRoute?id={id}") { backstackEntry ->
   backstackEntry.arguments?.getString("id")?.let { id ->
      Screen(
         onBack = { navController.popBackStack() },
      )
   }
}
a

ascii

11/03/2023, 2:37 PM
Yep NavController is not
stable
, so that could be why the lambda isn't memoized
b

Billy Newman

11/03/2023, 2:40 PM
So is this something I need to implement in every navigation screen that uses the topapp bar?
a

ascii

11/03/2023, 2:42 PM
I have something similar, saved it in a val and passed that around
Copy code
@Composable
inline fun <T> rememberCallback(
    key1: Any?,
    noinline callback: @DisallowComposableCalls () -> T,
) = remember(key1) { callback }
So in my case layout inspector reports 2 recompositions, but one of them is because I change title after a delay
b

Billy Newman

11/03/2023, 3:18 PM
Thank you, I truly appreciate your time!
Really sucks there is no mention of this in compose navigation docs
a

ascii

11/03/2023, 4:24 PM
That would be the correct way to do it because it allows you to test, for example via
TestNavHostController
. We don't have any integration tests for nav stuff yet, so it works well for us.
👍 1
👏 1
b

Billy Newman

11/03/2023, 6:06 PM
I do something very similar here for showing a snackbar and show a detail screen: https://github.com/android/nowinandroid/blob/f414cd482e67807b558653d3b9ef31f7fc4d1[…]/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt Debugging into this project show multiple recompose which seem to be due to the lambdas. I get that compose will only update if the state has changed so is this “normal”. I ask as I am trying to track down some performance issues in my app and seeing a recompose 4 or 5 times when state has only updated once is where I started
3 Views