https://kotlinlang.org logo
Title
a

abbic

02/20/2023, 10:10 PM
another q! im trying to implement my first viewModel, and linking it up with navigation isnt so clear. my instinct is to pass the router to the EventHandler, but that means passing it through a bunch of constructors and params which doesnt feel good. another thing is an event handler having access to the whole router means you lose control of what navigation events the screen has access to. the example on the website addresses the second point, and suggests passing the navigation event through a Screen callback
PostListScreen(
                        sort = sort,
                        onPostSelected = { postId: Long ->
                            // The user selected a post within the PostListScreen. Generate a URL which will match
                            // to the PostDetails route, by using its directions to ensure the right parameters are
                            // provided in the URL
                            router.trySend(
                                RouterContract.Inputs.GoToDestination(
                                    AppScreen.PostDetails
                                        .directions()
                                        .pathParameter("postId", postId.toString())
                                        .build()
                                )
                            )
                        },
                    )
and that could work, in fact i do something very similar on android, but it doesnt feel idiomatic to ballast? for example if we wanted to navigate not directly from a ui click (and in fact, from what i understand of ballast, it would be weird to drive anything from the ui except an
Input
anyway), but instead drive a navigation event from business logic, we would still have to pass this navigation lambda to the event handler manually. should i provide the router through a
single {}
and inject it into the event handler? maybe with an interface that only exposes navigation routes available on this screen?
this is for a plain desktop compose app
i think my sticking point, to summarise, is that i want to know how best to drive navigation from a Screen's EventHandler
c

Casey Brooks

02/21/2023, 4:33 PM
I wouldn’t necessarily say there’s a “right” or “wrong” way to communicate with the Router from a ViewModel, just tradeoffs of boilerplate vs. how coupled you are to the Router. For instance, calling
router.trySend()
directly from a Compose callback has no additional boilerplate, but couples the UI very tightly to the Router (as shown in your snippet). It also potentially brings logic into the UI, if the destination changes based on the state and not just the parameters given to the callback. On the other extreme, you could model each transition explicitly as an Event and leave the EventHandler to do all interactions with the Router, including generating destination URLs. This keeps you decoupled from the Router and explicitly describes the possible transitions between screens in the Contract, but adds a lot of boilerplate.
public object SettingsContract {
    public sealed class Inputs {
        public object BackButtonClicked : Inputs()
        public object ViewAllPostsButtonClicked : Inputs()
        public data class PostItemClicked(val postId: Long) : Inputs()
    }

    public sealed class Events {
        public object GoBack : Events()
        public object GoToPostList : Events()
        public data class GoToPost(val postId: Long) : Events()
    }
}
A middle-ground option would be to have the InputHandler generate the destination URL, which is then sent to the EventHandler to actually be processed. This keeps the navigation requests fairly independent of the Router library as you’re just requesting navigation to a given URL, which isn’t specific to Ballast Navigation. This is the way I’m currently using the Router in my application.
public object SettingsContract {
    public sealed class Inputs {
        public object BackButtonClicked : Inputs()
        public object ViewAllPostsButtonClicked : Inputs()
        public data class PostItemClicked(val postId: Long) : Inputs()
    }

    public sealed class Events {
        public object GoBack : Events()
        public data class GoToDestination(val url: String) : Events()
    }
}
And I wouldn’t worry about hiding the Router behind an interface if you’re using it from an EventHandler. The purpose of the EventHandler, in general, is to already be that level of indirection that separates the business logic (the InputHandler) from the platform-specific interaction
a

abbic

02/22/2023, 11:51 AM
ah yes! i see how its implemented in the kitchensink example. thanks :)