Is there an elegant way of integrating `Either` wi...
# arrow
l
Is there an elegant way of integrating
Either
with other wrappers? I have a
MutableStateFlow<Either<DataMissing, T>>
and want to update it only if it's a `Right`:
Copy code
val automations: MutableStateFlow<Either<DataMissing, AutomationList>>

    private fun handleEvent(event: EntityModel) {
        if (event is AutomationUpdatePendingStateEvent) {
            automations.update { automationsEither ->
                automationsEither.map { automations ->
                    automations.updateAutomationState(
                        automationId = event.automationId,
                        pendingState = event.deviceState,
                        provisioningComplete = event.provisioningComplete
                    )
                }
            }
        }
    }
Probably I have to write my own
fun <T> MutableStateFlow<Either<DataMissing, T>>.updateIfRight(function: (T) -> T)
s
Yes, I was going to say. This is probably the easiest.
Copy code
inline fun <E, A> MutableStateFlow<Either<E, A>>.update(f: (A) -> A) =
  update {
    when(it) {
      is Either.Right -> f(it.value).right()
      is Either.Left -> it
    }
  }
Then you can rewrite to:
Copy code
val automations: MutableStateFlow<Either<DataMissing, AutomationList>>

private fun handleEvent(event: EntityModel) {
  automations.update { list ->
    if (event !is AutomationUpdatePendingStateEvent) list
    else automations.updateAutomationState(
      automationId = event.automationId,
      pendingState = event.deviceState,
      provisioningComplete = event.provisioningComplete
    )
  }
}
l
I remember there was a monad which enabled monad composition, Reader Transformer. Although I don't know if
MutableStateFlow
could be treated like a monad
Also I don't know if it works without higher kinded types.
s
MutableStateFlow
wraps an atomic value, so it doesn't form a monad. It also doesn't have
map
or
flatMap
either IIRC.
l
Thanks for the quick help! I ended up with something like this:
Copy code
fun <T> MutableStateFlow<Either<DataMissing, T>>.updateIfRight(function: (T) -> T) = update { either ->
    either.map { function(it) }
}
s
You should be able to get away with
inline
there as well ๐Ÿ˜‰
Then it's completely penalty-free, and just syntactic sugar.
l
True, Android Studio didn't complain ๐Ÿ˜„
s
if you only update it when it is
Right
then why do you need the
Either
?
l
This is a very valid question which I'm asking myself right now as well ๐Ÿ™‚ The problem is that error handling is not really consistent in our app and there was never a definition what we should do if we get an error response from the backend (just show a toast with an error, show some empty screen, close the screen and go back to the previous one). The thing is I don't want to redesign the whole rest of the layers in the app right now. So I have to work around the current design somehow for the time being.
s
fair enough.... typically I'm using
MutableStateFlow
in the ViewModel to expose the state to the UI.... the state is typically a combination of sealed and/or data classes.... and errors from other layers typically map to some state (the state might display an error, but it is a valid state as far as the UI is concerned)
l
We have also introduced a
Repository
(kind of domain layer) which makes the calls to backend, listens to update events and exposes the different partial states of the system to the ViewModels.
These repositories also expose a StateFlow
The idea was initially to emit an error state when the initial API call fails and the data is missing. But it turns out to be not very practical, because if the data is missing there is nothing we (or the user) can do with the screen. So we should probably just emit a "fire-and-forget" event to just close the screen and show an error Snackbar/Toast. And when the user tries to enter the screen again, try again making the call. But it doesn't make sense to put the whole screen in an error/empty state if the backend failed and not give the user any possibility to retry etc.
On the other hand, our backend is generally very stable, so there are almost never errors. So this topic is right now pushed so far back in the minds of the Product Owners that we don't really get time to redesign it properly. As usual with small teams developing big products ;)
s
our backend is generally very stable, so there are almost never errors
on mobile most common errors would be connectivity issue ๐Ÿ™‚
l
That's true
That is not well handled in the app, but also in this case the user cannot do anything, because it's an online-only solution ๐Ÿ˜‰
Like I said, error handling is at the bottom of the backlog (if at all)
s
typically what I do in scenario like that is:
Copy code
// internally backed by StateFlow, but that is an implementation detail
fun state(): Flow<Thing>

suspend fun updateThing(): Either<Error, Unit>
new screen would subscribe to
state()
which never errors. If they need fresh data, they would call
updateThing()
, if this works, the
state()
flow would emit the latest value, if this errors, the screen can handle the error error is never part of the
state()
flow, so if other screens observe that flow, it's never cleared
this is something like CQRS
l
How do you pass the error from
updateThing()
? does it return
Either<Error, Unit>
?
s
just updated, but yes ๐Ÿ™‚
l
Also what about the
state()
before you first initialize it?
What does it return?
You have to pass a value to
MutableStateFlow()
on construction.
Or is it simply nullable?
StateFlow<Thing?>
s
Copy code
private val state: MutableStateFlow<Thing?>

fun state(): Flow<Thing> = state.filterNotNull()
that way it suspends until it's set to the first item, and also supports resetting
l
Ah, that's a cool approach
So you can show a loading indicator as long as the flow is suspended (on the first access)
HMm
But that wouldn't work actually
We have a
NotInitialized
state for this
When we emit it then the screen should e.g. show a loading indicator
s
So you can show a loading indicator as long as the flow is suspended (on the first access)
yes...
state.onStart { emit(Loading) }....
l
Ah ok
s
or
state.stateIn(viewModelScope, ..., default = Loading)
l
I will probably follow your approach when refactoring this, thank you for the helpful hints!
s
you are welcome... it's nice to chat and see what kind of problems other devs experience ๐Ÿ™‚
l
Oh yes, that's very helpful, because we often experience the same problems and reinvent the wheel
Especially regarding architecture, because it's hard to find resources about best practices here