https://kotlinlang.org logo
#ballast
Title
# ballast
r

Robert Jaros

12/24/2022, 12:50 PM
Hello. The
handleInput
function is defined as returning
Unit
and that makes the compiler complain when my handler is returning some nullable value.
e.g. when using this case I need to explicitly add
?: Unit
to get the code compiled:
Copy code
override suspend fun InputHandlerScope<MainContract.Inputs, MainContract.Events, MainContract.State>.handleInput(
    input: MainContract.Inputs
) = when (input) {
    is MainContract.Inputs.SetTozsamosc -> {
        manager.getTozsamosc(input.tozsamoscId)?.let {
            postInput(it)
        } ?: Unit
    }
}
Would you consider changing the
handleInput
signature so it returns
Unit?
instead?
c

Casey Brooks

01/04/2023, 4:13 PM
Sorry for the delay in this response, I was unplugged for the holidays and not checking messages. I would be hesitant to change the signature for a couple reasons. First, there’s not really a good deprecation path to this, and even though most people probably don’t explicitly include the return type in their InputHandlers, if they do this would make upgrading to the next version a big hassle. Second, it just strikes me as odd, given that
Unit
is the universal way to denote “this method doesn’t return anything useful”, and I can’t recall any other libraries/functions returning
Unit?
. But probably the bigger reason for not changing it is actually more about the proper usage of Ballast. Being built on the MVI model, which is at its heart Functional Programming, part of the “contract” is that the same State and Input produce the same result. In your snippet, that’s not necessarily true if
manager.getTozsamosc
returns null. So Ballast tries to keep you doing the “right thing” with its type system and error-checking, and in fact, running the null-case here should throw an error like
Input was not handled properly...
The solution is to either explicitly mark that code path with
noOp()
to acknowledge that you are handling this case and didn’t just miss a code path, or else to move that whole
manager.getTozsamosc
call into a
sideJob
.
postInput()
directly within the
handleInput
scope is technically an extension function that posts the Input from a sideJob anyway, so this is probably the more correct solution for this situation.
r

Robert Jaros

01/04/2023, 4:30 PM
I've already refectored out this
Unit?
problem from my code, so it's no longer an issue for me
I've created more complex app with Ballast (5 view models at the moment) and I've implemented some universal solutions for authentication, progress and error handling for my app. But my code definitely doesn't keep this "contract" you are talking about. How do you think the following typical workflow should be implemented: • we have some state and some generated UI representing this state (let's say a form with some data) • user interacts with the UI (e.g. clicks "Save" button") • the button sends an input to the view model • input handler processes the data (by interacting with external services) producing some results • input handler updates the state based on the results
Following your suggestion I should run a side job for every task.
But that means I would run a side job for almost everything.
Currently I handle this processing in my input handler without running unnecessary (?) side jobs. I'm using side jobs but for errors or authentication redirects.
c

Casey Brooks

01/04/2023, 4:57 PM
It’s definitely not a strict requirement, but more a guideline that can help avoid some potentially confusing behavior. Obviously, every app with have VMs that interact with external services, and it’s doesn’t make sense to move everything into a sideJob. That’s a ton of extra boilerplate, in a library that is already pretty heavy on boilerplate. But it’a also usually not necessary, and having everything running in parallel (in sideJobs) makes it harder to understand what’s going on, while the whole point of Ballast and MVI is to make things easier to understand and more predictable. It’s perfectly fine to suspend and execute those directly within the the inputHandler and update the state with
updateState { }
, rather than moving it to a sideJob and posting an Input back with the result. Just be aware of what suspending in the InputHandler means for the Input queue and your InputStrategy.
2 Views