rocketraman
03/21/2023, 9:39 PMChildCancelledException
, I guess because of the original input being cancelled.
Ideally the input handler would not need to "know" which methods on the repository are safe to call directly and which need to be executed in a side job.Casey Brooks
03/21/2023, 9:48 PMFifoInputStrategy
, then this should be safe, but if it’s using the LifoInputStrategy
(which is the default), it may not be safe. This is because the Repository may process the call quickly and update its state concurrently with the VM, which could then cancel the VM’s Input as it tries to accept the new Repository State. Moving it to a sideJob would definitely solve the problem with either case, but adds considerable boilerplate that isn’t ideal.
Since I released this library 1.5 years ago, I’ve come to realize the FifoInputStrategy
is probably a better default, but it’s a bit difficult to switch the default without potentially breaking existing projects. I’m trying to figure out a gently migration strategy, but probably will not be included in 3.0.0. I’d suggest explicitly setting the InputStrategy for your VMs and Repos to be FifoInputStrategy
, but be aware that it may introduce delays between an Input being sent and it being processed by the InputHandlerrocketraman
03/21/2023, 9:50 PMFifo
-- that should lead to more predictable behavior in general.rocketraman
03/21/2023, 9:53 PMCasey Brooks
03/21/2023, 9:54 PMCasey Brooks
03/21/2023, 10:00 PMrocketraman
03/22/2023, 1:22 PMinterface BallastDependencyKeyed {
fun keys(): Set<Any>
}
Any State
or Input
could implement this interface, and by doing so it expresses the idea that any other State
or Input
that returns keys()
that intersect with it, are dependent / related.
Given the inputs:
A(keys=[1]), B(keys=[2]), C(keys=[1])
A default LIFO strategy would cancel A and skip B on receipt of C. But with the dependencies we can express the idea we want to skip B on receipt of C, but not A. Or minimally:
A(keys=[1]), B, C(keys=[1])
A default FIFO strategy would still execute A and B on receipt of C. But with the dependencies we can express the idea that we are allowed to skip B. Or minimally:
A, B(keys=[2]), C
A parallel strategy would execute them all in parallel by default. But with the dependencies we can express the idea that A and B can run in parallel, but C has to wait until A (and B because it is running in parallel with A) are done (fork-join style parallelism).
Not implementing the BallastDependencyKeyed
interface for the state type or any input results in the same behavior as today.Casey Brooks
03/22/2023, 2:31 PMCasey Brooks
03/22/2023, 6:09 PMCasey Brooks
03/23/2023, 5:21 PMrocketraman
03/23/2023, 5:23 PMrocketraman
03/23/2023, 5:25 PMrocketraman
03/23/2023, 5:26 PMrocketraman
03/23/2023, 5:27 PMCasey Brooks
03/23/2023, 5:31 PMrocketraman
03/23/2023, 5:33 PMrocketraman
03/23/2023, 5:35 PMand actually is the default strategy Ballast uses (though it can be changed).
rocketraman
03/23/2023, 5:37 PMI may be misunderstanding what you mean by “Could the default behavior of LIFO be to do LIFO as now, but not cancel any already ongoing operations?“, though. Could you clarify that a bit?Actually, maybe I think there is something here. In the examples you gave of a user navigating away from a page or typing something into a filter list, another input would be received. This would cancel the previously running input. That isn't the behavior I saw. The behavior I saw was that an input cancelled itself simply by updating state.
Casey Brooks
03/23/2023, 5:42 PMrocketraman
03/23/2023, 5:48 PMonClose
event which sends a "DismissPopup" input. That event is meant to trigger when the popup is closed by the user, but it is also triggering when the popup is closed via a state change.rocketraman
03/23/2023, 5:49 PMDismissPopup
input is canceling the running input, exactly as you said.Casey Brooks
03/23/2023, 5:49 PMrocketraman
03/23/2023, 5:49 PMCasey Brooks
03/23/2023, 5:49 PMrocketraman
03/23/2023, 5:51 PMonClosed
and onClosing
events, trying to figure out what onClosing
does.rocketraman
03/23/2023, 5:52 PMrocketraman
03/23/2023, 5:53 PMCasey Brooks
03/23/2023, 5:55 PMscrimClickAction
or escapeKeyAction
(https://github.com/mpetuska/kmdc/blob/master/sandbox/src/jsMain/showcases/MDCDialog.kt#L88-L89) might be a better way to be explicit about when the user chooses to close the dialogrocketraman
03/23/2023, 5:57 PMonClose
callback, and it is being called by both the dialog onClosed
event, AND when the user explicitly clicks the close button.rocketraman
03/23/2023, 5:58 PMCasey Brooks
03/23/2023, 6:04 PMonClosed
is called, which makes the dialog not strictly obeying its open
value. Might be “good enough” for the example I linked above, but in a true MVI/UDF model, this dialog implementation is not strictly obeying its contract.
Obviously, it can only do so much when it’s trying to synchronize “imperative JS” with “declarative compose”. Synchronizing state with traditional UIs is incredibly difficult to get right, which is probably why the Compose team chose to just rewrite Android’s UI rather than make it a layer above the existing View system 😅. But it might be worth opening an issue to the KMDC repo about this behavior, at least to get the maintainer to look deeper and see if this actually what’s going on hereCasey Brooks
03/23/2023, 6:06 PMCasey Brooks
03/23/2023, 6:07 PMrocketraman
03/23/2023, 9:20 PMscrimClickAction
or escapeKeyAction
to be explicit about whether the user chose to close it, or its closing based on an input. The out-of-the-box behavior is for the onClose
event detail action
to be null
when not closed by the user, and `scrimClickAction`/`escapeKeyAction` when closed by the user using either of those two methods (which defaults somewhat confusingly to "close").rocketraman
03/23/2023, 9:22 PM