Does anyone interact with a screen-level view mode...
# compose
c
Does anyone interact with a screen-level view model from the TopAppBar? I imagine actions like a save or search might live in a TopAppBar nicely. However, the separation between the TopAppBar and Content in Scaffold makes interacting with the view model from the TopAppBar non-obvious. Is it worth hoisting the view model from screen-level to activity-level just for a few simple actions? Perhaps there is a better way to integrate them that I'm not aware of
s
Maybe related to this message https://kotlinlang.slack.com/archives/C04TPPEQKEJ/p1688073594525389?thread_ts=1688073594.525389&cid=C04TPPEQKEJ I assume your issue comes from having a global scaffold. There's three approaches I suppose • You move those per-screen, so it's easy to access it. • You move that logic on a ViewModel (or a normal state holder) which is scoped to your activity instead, so it lives for as long as the top app bar lives. • You pass down callbacks to the top app bar actions all the way down to those screens where you want to do something with them (like call a screen level VM function in your case)
c
Using a per-screen scaffold is interesting to me. The more I read into this topic, the less clear the purpose/value of separating state between the top app bar and the screen composable/content anyhow. I've also explored using
NavBackStackEntry.LocalOwnersProvider
(link) to pass the LocalViewModelStoreOwner from the nav destination to the TopAppBar. However, the documentation makes it sound like doing so might mark the NavBackStackEntry to live as long as the TopAppBar composable... which defeats the purpose of separating the content from the app bar in the scaffold in the first place?
Copy code
val navController = rememberNavController()
val currentBackStack by navController.currentBackStackEntryAsState()
val currentDestinationFromStack = currentBackStack?.destination
val currentDestination =
    allDestinations.find { it.route == currentDestinationFromStack?.route } ?: HomePageRoute

val navBackStackEntry by navController.currentBackStackEntryAsState()
val stateHolder = rememberSaveableStateHolder()
Scaffold(
    topBar = {
        navBackStackEntry?.LocalOwnersProvider(stateHolder) {
            MyTopAppBar()
        }
    },
    bottomBar = {
        MyNavBar(
            allDestinations = allDestinations,
            currentDestination = currentDestination,
            onScreenChange = { newDestination ->
                navController.navigateSingleTopTo(
                    newDestination.route
                )
            })
    }
) { innerPadding ->
    MyNavHost(navController = navController, modifier = Modifier.padding(innerPadding))
}
s
Maybe good to ask here, what is it that you want to do from those top app bar buttons? Are you trying to get the back button work on it, or is this also about the actions on the right side?
c
Thanks! I'm specifically concerned with the actions on the right side. Currently I'd like to add a "save" button to submit form data to a server. But some of the sample actions in the material 3 spec online would also have this issue I imagine (attaching, filtering, selecting a date, etc.). I'm not worried about the back button, as that only needs to interact with the nav controller, and seems well abstracted for that case. Interacting with screen content from the top app bar seems tricky though
FYI - I've decided it's best not to access the viewmodel store elsewhere. In the case of my TopAppBar example... I moved the bar to a per-screen scaffold instead, and only use a global scaffold for a bottom app bar with navigation only. This way the state-aware TopAppBar lives in the same lifecycle as it's related state. Whereas, the bottom nav bar can live longer than each screen and finish it's click animations/etc. when navigating between those screens
s
Yup, that’s what I do too. Navigation bar is global (Bottom or NavRail) and then everything else is screen-specific.
b
@Stylianos Gakis with a global navigation bar do you run into any issues trying to present Full Screen Dialogs etc and having it go all the way up to the top below the status bar? What about having the TopAppBar have a scrim applied to it when showing a ModalBottomSheet?
s
For sheets I use
androidx.compose.material3.ModalBottomSheet
which you can just use from anywhere and they are rendered on top of everything (on their own Window I think) For dialogs I use
androidx.compose.ui.window.Dialog
same thing as above These two apply the right scrim and show at the right height all the time pretty much, haven’t had issues. For TopAppBar that is not global at all, that is per screen if they need it. I got a custom Scaffold to make it easy to make those screens use it, so again nothing special needs to be done with it for this to work out.
b
@Stylianos Gakis So you're saying that TopAppBar is per screen? I feel like doing this approach would solve a lot of our issues and we've talked about doing it, though we would lose some stuff like animating the title as we transition.
s
Yeah animation is the one thing which I can’t really support well using this approach. But I really must tell you it’s so much simpler for eeevery other situation. Hopefully if we ever get some shared element transitions we can also work around the animation issue too, but IMHO not having too great transition animations on the top app bar is a very valid drawback for the mess that the top app bar becomes if you need to hold a global one which is different for literally every single screen. The only reason it works with the bottom navigation bar (or NavRail) is that it’s either showing or not, and it’s static layout (or almost always is) but does not rely on which destination you’re in, it just shows or doesn’t show.
b
@Stylianos Gakis we are currently using a Material 2 bottom sheet for some reason and not a material 3, so I'm not sure if the behavior of that is different. Let me attach a screenshot of how the scrim looks with this ModalBottomSheetLayout + the Global TopAppBar
s
Alright! Also, just so you know, there’s no need to tag people if they are already part of the thread. I am getting a notification that there’s a new message anyway, so you can just reply without the extra ping, it’s nicer for me that way 😊
b
as you can see here there is no scrim applied to the top app bar, which to me is another indication of why a global app bar seems like a hassle to handle. Also while the content behind the scrim is not selectable the top app bar is.
s
What are you using to show the dialog itself?
b
Material 2 ModalBottomSheetLayout
s
Right, that’s a different kind of bottom sheet I think, it’s one that’s attached to the layout itself. While the one I was suggesting shows the dialog on a separate window on top of everything else completely. Not sure if a material2 equivalent exists (we don’t use m2 for this) but you can either try to find one, or you can try to see if the m3 one suits your needs.
b
oh i see, so perhaps a material 3 ModalBottomSheet covers the entire screen instead of just the screen content?
s
Yeah it’s not part of some layout exactly, it’s using a different mechanism so that it simply shows on top of everything. Again, I think this is a completely separate Window on top of everything, but not 100% sure. In any way, it does work for this requirement for us at least.
b
cool, we are trying to get everything to be material3 anyway so we will work on getting that refactored and see what happens. as far as the dialog is concerned, do you just override systemDefaults and set the maxWidth and maxHeight to fill? How does the Dialog not stop below something like a global top app bar? Separate DialogFragment or?
i.e. I'm concerned we can't get a dialog that looks like this while having a global top app bar since the screen that shows the dialog is contained below the top app bar basically.
Just played around with the dialog we're using for a logout alert, and it seems like it doesn't care about where it's contained. If you set full height and width it'll cover the whole screen.
s
That dialog does not take up the entire screen, it looks something like this https://m3.material.io/components/dialogs/specs, but not the full screen dialog, I don’t know if that full screen dialog even exists in compose material right now, does it?
b
that screenshot I took is from m3 page about full screen dialogs. I guess I'm confused when you say not the full screen dialog.
I guess dialogs in android aren't part of any material package, which is really interesting since they're also defined in material. makes me wonder if the ui.window package is trying to make the dialog similar to material specs or if it has nothing to do with it.
Oh I guess an AlertDialog is a Material 3 component.