Toolbar structured behavior for Jetpack Compose

1.6k views Asked by At

Imagine the usual behavior of a toolbar in Android.

You define a Toolbar widget in the Activity, and can access it using onCreateOptionsMenu and onOptionsItemSelected inside your fragments.

However, something like this is not possible with normal Jetpack Compose, as there is no way of accessing the Toolbar that is defined in the Activity's Scaffold.

So think of this scenario. You have an Activity, with the Scaffold defined in it, and a NavHost inside that Scaffold. The NavHost contains all the sub-pages of your application (Other Composables). The title can be handled view the Navigation Destination Listener, what remains is the Actions of the Toolbar.

How would you change the toolbar actions depending on the current page/composables you're in? And handle clicks on these actions?

P.S : Using a Toolbar in each of the pages is not a solution, as it makes for a bad user experience when switching between animated pages, where the toolbar will disppear and reappear on each page.

2

There are 2 answers

4
Gabriele Mariotti On

You can use the TopAppBar at a Scaffold level and use your current destination to customize the topBar.

Something like:

topBar = {

    // Use your logic here
    val currentDestination = navBackStackEntry?.destination
    if (currentDestination == ....) {
      CustomAppBar()
    } else {
       TopAppBar(
          title = { /*...*/ },
          actions = {
                if (currentDestination == ....) {
                    IconButton(onClick = { /* doSomething() */ }) {
                        Icon(Icons.Filled.Favorite, contentDescription = "")
                    }
                }
                IconButton(onClick = { /* doSomething() */ }) {
                    Icon(Icons.Filled.Add, contentDescription = "")
                }

            }){ //... }
    }
}

Otherwise just use separate TopAppBar in each screen.

3
AudioBubble On

I used an interface I named ToolbarController which contained callback methods that could set the value for the variable(s) used in the the call to scaffold's TopAppBar:

@Composable  
fun MyApp(){  
  
 var toolbarTitle by remember{ mutableStateOf("") }  
  
 // ToolbarController would be some interface you have defined  
 val toolbarController = object: ToolbarController {  
        override fun setTitle(title: String){  
            toolbarTitle = title  
        }  
    }  
  
 Scaffold(  
    topBar = { 
       TopAppBar( title = { Text(text = toolbarTitle) }  )  
    }  
 ){  
    SomeScreen(toolbarController = toolbarController)  
 }  
}  
  
@Composable  
fun SomeScreen(  
    toolbarController: ToolbarController  
) {  
    //I'm not 100% sure I need to use an effect here, but I think so...
    //And I'm not sure this is the right one. It is not a coroutine I call,
    //but it of course works with normal calls. Also SideEffect runs on every
    //recompose according to the docs, and that's not what we want.
    //https://developer.android.com/jetpack/compose/side-effects
    LaunchedEffect(true){
       toolbarController.setTitle("Some screen title")  
    }
}

Edit: And it is easy to use it for any of the toolbar properties, you could create the interface like this:

interface ToolbarController{
    fun configToolbar(
        title: String = "",
        navigationIcon: IconButton? = null,
        actions: List<IconButton> = listOf()
    )
}

The point is that you just make callback functions and run them in LaunchedEffect. That is one way to set toolbar properties from within a composable in the scaffold. The interface stuff is just a way to group these callbacks so it don't get too messy.