Robert Jaros
12/18/2022, 9:32 AMIllegalStateException: Nothing can be processed until the state has been restored
is thrown
2. The saved state module is not working anymore (doesn't save anything)Robert Jaros
12/18/2022, 9:34 AMsetTimeout({}, 0)
block.Robert Jaros
12/18/2022, 9:34 AMRobert Jaros
12/18/2022, 9:37 AMRobert Jaros
12/18/2022, 9:39 AMonRestoreComplete
callback but I don't have access to the viewmodel from there.Casey Brooks
12/18/2022, 7:43 PMIllegalStateException
is thrown, the interceptor gets killed and can’t recover, which is why it’s not saving the state after that point.
But setting a delay is not an idea solution for sending the initial input. It’s not necessarily guaranteed to always work, which is why the onRestoreComplete
callback is the intended way to initialize the VM strictly after the state has been restored. It’s also not meant to be combined with BootstrapInterceptor
or have an initial Input sent any other way.
Why do you need access to the ViewModel in onRestoreComplete
? Why is it not enough to just send the initialization input from there? You might need to tweak the initialization logic a bit to fit into the SavedState module’s way of working, but I may have missed something that needs to be fixedCasey Brooks
12/18/2022, 7:46 PMNothing can be processed until the state has been restored
error is thrown: it’s likely the SavedState restoration will overwrite anything that would have been processed by another Initialize-type Input. So to keep things working as expected and avoid race-conditions (because adapter.restore()
is suspending and running in parallel to the main VM Input queue), it forcibly checks that nothing is done until the state has been restoredRobert Jaros
12/18/2022, 7:48 PMRobert Jaros
12/18/2022, 7:48 PMCasey Brooks
12/18/2022, 7:55 PMRobert Jaros
12/18/2022, 7:56 PMCasey Brooks
12/18/2022, 7:59 PMrestore()
or onRestoreComplete()
. This would require those routing params to be available to the VM’s constructorCasey Brooks
12/18/2022, 8:01 PMonRestoreComplete
. And you can use that as a signal for the UI to then send the follow-up Input containing the routing params. This would work without needing to have the params available in the constructorCasey Brooks
12/18/2022, 8:04 PMHowever, I would prefer the initial inputs be queued/buffered by the view model as the state is restored and then processed.The
FifoInputStrategy
does allow buffering those Inputs, but the problem with state restoration is that it would require re-ordering Inputs to process everything as expected (where the routing params are sent to the queue but not accepted until the state is restored)Robert Jaros
12/18/2022, 8:05 PMCasey Brooks
12/18/2022, 8:06 PMRobert Jaros
12/18/2022, 8:08 PMRobert Jaros
12/18/2022, 8:09 PMRobert Jaros
12/18/2022, 8:14 PMonRestoreComplete
so it can return an Input. But it would be helpful to be able to return an Event as well.Casey Brooks
12/18/2022, 8:18 PMRobert Jaros
12/18/2022, 8:21 PMonRestoreComplete
returning an Input is kind of duplication because the input is just for making a new state and the current state was just created by the restore method.Robert Jaros
12/18/2022, 8:22 PMCasey Brooks
12/18/2022, 8:32 PMrestore()
. And the main reason for sending a follow-up Input isn’t really to set more state, but to execute some logic that needs to be done after the state is set, like starting to observe flows in a sideJob.
And I do get the idea behind sending an Event from the onRestoreComplete
(or Interceptors in general), but the current implementation does not allow for this. And now that you bring it up, and considering that sideJobs can send events directly, it probably would make sense to allow Interceptors to directly send Events.
However, the library would need some breaking changes to enable this, which I’m trying to avoid right now until Kotlin 1.8.0 gets released. So I’m mostly just trying to see if there’s a way to accomplish what you need in a way that isn’t terrible with the Ballast APIs that are currently availableCasey Brooks
12/18/2022, 8:48 PMonRestoreComplete
, that itself send the Event you need.
Here’s another solution that may be a bit better and require less boilerplate
public object MyScreen {
public data class State(
val restoredState: String = "",
val isRestored: Boolean = false,
)
}
public class Adapter : SavedStateAdapter<MyScreen.Inputs, MyScreen.Events, MyScreen.State> {
override suspend fun RestoreStateScope<MyScreen.Inputs, MyScreen.Events, MyScreen.State>.restore(): MyScreen.State {
return MyScreen.State(
restoredState = getRestoredState(),
isRestored = true,
)
}
}
public fun CoroutineScope.initialRoutingParams(vm: BallastViewModel<MyScreen.Inputs, MyScreen.Events, MyScreen.State>) {
launch {
val restoredState = vm.observeStates().first { it.isRestored }
vm.send(MyScreen.Inputs.RoutingParams)
}
}
Robert Jaros
12/18/2022, 9:26 PMRobert Jaros
12/18/2022, 9:30 PMCasey Brooks
12/18/2022, 9:47 PMDispatchers.Default
unless changed (since it’s the only one available in common code). I was hoping to avoid actual/expect in the core module, but I’ve been thinking that it’s probably worth introducing it for picking the most appropriate dispatchers for each platform by default