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

David Attias

11/16/2020, 5:40 PM
I'm struggling to implement a rather simple behaviour (automatic navigation without user interaction) using the recommended Architecture pattern where only a ViewModel is passed to a Composable and some navigation block to trigger on a user interaction. I've tried to pass a route as a State to the Composable (which triggers navigation when not null) in order to treat it as simple data in the ViewModel but I'm then stuck because when the user hits Back it navigate him back to the most recent screen automatically. I think I would need some kind of transient state which is restored to null when processed by the Composable view but I really don't know how to do it... Any input/help would be very very appreciated 🙏
a

Adam Powell

11/16/2020, 6:43 PM
When you find yourself wanting to reset or null out a state or otherwise mark something as consumed during composition, that's a sign that you have an event that should be processed into state instead. Think of your route like a URL bar; it stays put the whole time you're at that location in the app, it's not consumed by the act of navigating to it.
d

David Attias

11/16/2020, 8:00 PM
If I understand well, what you mean is that my current architecture where i set the route "to navigate to" as a State of my composable is the right way to solve my problem ?
j

Jeremy

11/16/2020, 8:50 PM
I think generally you'd want
data class MyState(val currentScreen: String)
and then you'd have
if(state.currentScreen=="a") { ScreenA() ) }
So rather than stating "navigate to screen a" you're stating "screen a should be currently visible"
1
You can specify the back behavior to load whichever screen you want
d

David Attias

11/16/2020, 8:56 PM
I understand what you suggest but it means you don't use the Navigation component and the NavGraph, you're making your own navigation by using conditional logic inside a Composable right ?
j

Jeremy

11/16/2020, 11:15 PM
Yes
ui = fn(state)
Are you using
navigation-compose
lib or building a custom solution?
d

David Attias

11/17/2020, 7:54 AM
I'm using
navigation-compose
a

Archie

11/17/2020, 9:43 AM
I actually have the same idea of make the route a state...
Copy code
class MyViewModel : ViewModel() {
    val currentRoute: MutableStateFlow<String>()

    ....
}

@Composable
fun MyScreen(
    navController: NavController,
    myViewModel: MyViewModel,
) {
    val route = myViewModel.currentRoute.collectAsState(startRoute)
    navController.navigate(route)
}
But I don't how
popBackStack()
or even
popBackStack(id)
would be done this case? Any idea?
d

David Attias

11/17/2020, 9:51 AM
do you mean the popBack to MyScreen or from MyScreen ?
a

Archie

11/17/2020, 9:52 AM
@David Attias regardless? i guess?
d

David Attias

11/17/2020, 9:55 AM
So, my problem with this architecture is indeed the fact ,once you navigate to the other screen and that when you will want to popBack to
MyScreen
, it will immediately navigate back to the new route....
That's why I don't beleive this is the right solution and I don't find any other good solution with the current architecture...
I'm currently thinking to add an external
Navigator
that will handle navigation with event from the ViewModel....
a

Archie

11/17/2020, 10:06 AM
How would that work? Could you show it? I kinda think that to do
popBackStack()
in this architecture, we would also have to set the
currentRoute
to
previousRoute
, which means holding on to the
backStack
(consumed route) itself. I hope there would be an official sample for this in the future...
d

David Attias

11/17/2020, 10:19 AM
I'm currently trying to implement it, I will share it asap. I'm currently stuck, I can't wait for an official way to do it...
👍 1
@Ian Lake I don't know if we miss something, but it doesn't sounds like something very special that we want to do, is there a right way to do it for now with
navigation-compose
?
a

Adam Powell

11/17/2020, 4:30 PM
I'm still not quite sure I understand; if you're using navigation-compose you already have an object with methods you can call to navigate to a destination in response to an action, right? 🙂 if you have a state that represents what content you're looking at and the stack of content beneath it, that's a form of navigation construct as well. If you handle the back button, you can nest one inside the other if, when, and where it makes sense for the app
d

David Attias

11/17/2020, 4:35 PM
Yes but my use case is that I don't have a user interaction, I want to initiate a navigation based on a ViewModel logic so it doesn't fit with the typical navigation-compose examples where the navigate() is injected in a button callback.
a

Adam Powell

11/17/2020, 4:38 PM
can you call navigate from the viewmodel method?
d

David Attias

11/17/2020, 4:39 PM
I could but from what I read, it sounds like a bad practice to reference the NavController in the ViewModel
a

Adam Powell

11/17/2020, 4:40 PM
I think we're coming around to the desire for observing a callback or event again then, to launder that call a bit in the abstraction layering 🙂
the key I was referring to about state vs events is still important; a, "navigate to X" event is an event, and lots of trouble results from trying to model that as state
d

David Attias

11/17/2020, 4:45 PM
Yes, you're right, this is a bad idea to model that as a state
a

Adam Powell

11/17/2020, 4:48 PM
If you're looking for a way to bridge a channel-derived flow of viewmodel events to navigation calls when the navigator lives in a composable, this pattern for managing the scope of an observer without its events becoming state might be useful:
Copy code
LaunchedEffect(navigator, viewModel) {
  viewModel.navEvents.collect { navigator.navigateTo(it) }
}
d

David Attias

11/17/2020, 4:55 PM
Thanks, I will look into that pattern to see how it could fit my needs.
h

harry248

03/12/2021, 8:04 AM
@Adam Powell Is this still the recommended way to trigger navigation from a ViewModel / to handle one-shot events?
a

Adam Powell

03/12/2021, 2:44 PM
All the same principles of distinguishing state vs. events still apply, yes. Where you scope a collector/observer may vary based on your app, the above snippet is just one example.
👍 1