Hey <@UD17D4K8W>, I too sometimes encounter issue...
# ballast
s
Hey @Casey Brooks, I too sometimes encounter issues like these where I for example want to initialise a homepage by loading local objects and multiple remote objects and once 1 of them completes, the other routines get cancelled. When I use FIFO, this problem seems to resolve itself but I'm still left wondering what the "optimal" approach is?
F.e. I'm initialising my home screen which has swimlanes with rows of data, some coming from local storage, others from public remotes and one from an authorized endpoint:
Copy code
when (input) {
    is ExploreScreenContract.Inputs.InitMainScreen -> {
        sideJob("LoadDraftRoutes") {
            postInput(ExploreScreenContract.Inputs.LoadDraftRoutes)
        }
        sideJob("LoadUserRoutes") {
            postInput(ExploreScreenContract.Inputs.LoadUserRoutes)
        }
        sideJob("LoadPublicRoutes") {
            postInput(ExploreScreenContract.Inputs.LoadPublicRoutes)
        }
    }
My original approach was just having a
LaunchedEffect(Unit)
inside of the main composable which posts the 3 inputs, but there I crashed into the LIFO issue. In this latter case I really thought that creating a sidejob for all 3 inputs would resolve it but even then I still get the child cancellation exception. Is changing the strategy to FIFO really the only option when you encounter a case such as this one?
c
To make sure I’m understanding correctly, what you are needing is to load all data in parallel, and let each one update the VM state independently without cancelling the others, correct? To start, yes I would recommend using FIFO as the InputStrategy, as it will be the most natural way to think about the data processing. With FIFO, just like the main thread of your app, you want to make sure you move long-running work off the main queue so it is running in parallel and allowing other work to be processed. For the main thread, that means moving heavy work to a background thread rather than running IO on the main thread, and in Ballast that means making the IO calls from within a sideJob. The default is LIFO for historical reasons, but it is called out in the docs that FIFO is strongly recommended as your default InputStrategy to avoid confusing issues like this. However, you should be performing the actual IO calls from within the sideJob, not just using them to post an Input which loads it. In your example, with FIFO it essentially schedules all Inputs to load, but they will be loaded in sequence, since each Input gets queued and processed one-by-one. In LIFO, they will be cancelled so that only the last one posted will run. So what you want do is something more like this. Notice how each Input will run and complete quickly, either moving the IO work to a sideJob or just updating the state.
Copy code
when (input) {
    is ExploreScreenContract.Inputs.InitMainScreen -> {
        sideJob("LoadDraftRoutes") {
            val draftRoutesResponse = getDraftRoutesFromLocalStorage()
            postInput(ExploreScreenContract.Inputs.DraftRoutesLoaded(draftRoutesResponse))
        }
        sideJob("LoadUserRoutes") {
            val userRoutesResponse = getUserRoutesFromAuthenticatedApi()
            postInput(ExploreScreenContract.Inputs.UserRoutesLoaded(userRoutesResponse))
        }
        sideJob("LoadPublicRoutes") {
            val publicRoutesResponse = getPublicRoutesFromPublicApi()
            postInput(ExploreScreenContract.Inputs.PublicRoutesLoaded(publicRoutesResponse))
        }
    }
    is ExploreScreenContract.Inputs.DraftRoutesLoaded -> {
        updateState { it.copy(draftRoutes = input.routes) }
    }
    is ExploreScreenContract.Inputs.UserRoutesLoaded -> {
        updateState { it.copy(userRoutes = input.routes) }
    }
    is ExploreScreenContract.Inputs.PublicRoutesLoaded -> {
        updateState { it.copy(publicRoutes = input.routes) }
    }
}

suspend fun getDraftRoutesFromLocalStorage() { ... }
suspend fun getUserRoutesFromAuthenticatedApi() { ... }
suspend fun getPublicRoutesFromPublicApi() { ... }
👍 2
r
FIFO mode just works... But LIFO mode does a great job at exposing when Ballast, or some other framework in the system as was my case, isn't being used correctly or idiomatically.
s
Ok I see now, thanks for the explanation! 👍
Last question in regards to this, is it also possible to scope your InputStrategy? Taking the above example, let's say we only want
InitMainScreen
to be interpreted as FIFO and the rest of the of the inputs as LIFO. Would this require creating a separate Input-/EventHandler + ViewModel trio or can the scoping f.e. be done by input?
r
c
That’s right, LIFO/FIFO is set once for the entire VM and isn’t easy to change. You can create a custom InputStrategy to accomplish that if you need, but I think it would be easier to just order the loading of data as you want within a sideJob, since it’s ultimately just a coroutine. The need for an InputStrategy is that users may interact with the UI and Inputs can be sent at any time, so the InputStrategy gives you control over the inherently uncontrolled user-generated Inputs. But when writing code for loading data for a screen, you already have all the tools necessary to control that process with coroutines, so there’s no need to complicate the InputStrategy for this narrow use-case.