I'm currently porting an application from Jetpack ...
# compose-desktop
s
I'm currently porting an application from Jetpack Navigation to Decompose. I'm stuck on trying to pass a value from a child screen to the parent screen. With Jetpack Navigation I could observe some LiveData associated with the backstack entry and pass data to the parent backstack from the child. I would like to do this in a way that the parent and child are not coupled. Does anyone have a suggestion? My first thought was to maintain the state in the root component, but I don't see how to do that in a way that only the parent can observe it.
a
If I understood the question correctly, you should be able to just pass a callback to the child screen.
s
Maybe there's something that I'm missing. I have the child built from a Parcelable class, so I don't see how to pass a callback.
a
Parcelable configurations are just child descriptors (or keys), they only hold minimal information required to create a child. This information is persisted on Android, e.g. on configuration change, process death, etc. But the main thing is that you manually create a child given a configuration, in the
router(childfactory = { ... }
function. This is the place where you can supply absolutely any dependency - a database, a network client, or a callback. Take a look at the examples.
s
Thanks. Looking into it.
👍 1
I don't see how to get the callback from ScreenA without coupling ScreenB to ScreenA. My thought is to add a map to the root component and pass a key to ScreenB
a
You pass a callback from a parent to a child. The parent is only aware of its immediate children. Children are not aware of the parent.
s
I guess I need to restructure my components. I said parent/child, but I guess they're more like siblings.
a
Alright. So you need to send data between siblings. In this case there are two ways. 1. Create a PublishSubject (or a SharedFlow) in the parent, pass it to one sibling which will push data to it. And also pass it to another child which will subscribe to it. 2. Pass a callback to one child, when data is received - locate another child and call its method and pass data there. You can perform data transformation (mapping) in the parent level to avoid coupling.
s
Thanks! My tentative solution is to use a MutableStateFlow with a Map and key. The map and key to allow multiple concurrent listeners. One component adds data to the map, the other removes it.
Now I'm thinking it might better to make the receiver implement a certain interface and from the sender pop the router, and call the receive method of the activeChild if it implements that interface
Thanks for your help and for the library. Would it be interesting to have a sample using ViewModels? I'd be willing to write one if so. It might help me make a bit more sense of what I'm doing here also.
a
It's not clear why you want StateFlow instead of SharedFlow? The former is conflated, the latter isn't. So if you send multiple values sequentially, the receiver may miss some of them. And what is the purpose of the Map and the key?
s
Google recommends using state instead of streams. I'm not clever enough to handle edge cases. https://developer.android.com/topic/architecture/ui-layer/events It's in the note at the end.
In my case, there are never multiple values because it's a return value from one screen to another. If there were, it could be handled by creating a list and removing elements as they're handled.
a
I believe that note is for exposing UI events from a ViewModel and consuming them in the UI. It should indeed be performed via state. But communication between two ViewModels (or Decompose components) is more natural via streams of one-time events (or just normal callbacks).
s
The map/key is to handle the situation where two screens on the stack process events. For instance: Screen C is the one returning a value. Screens A and B can navigate to Screen C, so they both observe the return state. If they are both on the stack observing the return state, there needs to be a way to differentiate which screen should handle the state. Using a map and key seemed the most obvious for this.
a
If that's a return value, then you can just add
onFinished: (Data) -> Unit
to one component. Then in the root supply this callback. When called, pop the screen from the stack, and call the another component's method with the result.
s
Ok, I'm going to create a simple example with that method.
👍 1
I think this turned out pretty well. It's a pretty close approximate to what I'm trying to do. https://github.com/sproctor/decompose-example
a
Yep! This should work. Just keep in mind that there is one corner case when the navigation might be performed asynchronously - when the Router is called recursively. So it is safer to use the
onComplete
callback -
router.pop { <the rest of the logic> }
. More information can be found in the [docs](https://arkivanov.github.io/Decompose/router/navigation/#the-navigation-process).
s
Ah, thanks!
If you want another sample, feel free to use that
👍 1