Ok so want to see if someone already solved this o...
# getting-started
j
Ok so want to see if someone already solved this or library existing. I often want to combine Flow and FlowResult. With Result I mean Kotlin ResultT>. If I have Result A + Result B where any of them fail all fail. So if all success then its success. I can use Result.fold but Imagine combine 10 results or flows. I tried vararg but not supporting Result and also I loose type safety. Is there a smart chain pattern, Tuples, Arrows, Either or anything Kotlin vanilla I can use solve this generic? In reality often want like:
Copy code
combineFlowResults (
  flowA, flowB, flowC, flowD
) { a, b, c, d -> 
   UiStateOf(a, b, c, d)
}.onStart(Uistate.Loading)
e
Don't use
kotlin.Result
.
j
@ephemient Well, please tell me what to use if thats not good. Also why does it even existing?
e
as the comment says, use your own sealed classes, for example
Copy code
sealed interface Result<out T> {
    data class Success<out T>(val value: T) : Result<T>
    data class NetworkError(val exception: IOException) : Result<Nothing>
    // etc.
modeled after what actually happens in your application, not just some untyped
Throwable
as
kotlin.Result
does - or use Arrow Either or some other alternative which also allows for modeling that
kotlin.Result
exists for the sake of some generic infrastructure like Kotlin coroutines
j
Thanks not helping. Ignoring fact if using Result or not thats not my original question. How combine a bunch of Flow, let say using custom result sealed class, what should I do to generic transform this?
e
if you are using your own Result class, the
combine
overload will work fine
j
It will not as I still need to check if all my sealed classes is success or error and transform to ui state
e
you should.
j
I use combine today but trying solve this generic in compose.
Need to remember the flow first and then collect the transformed state.
So back to what I already have then :) I was hoping there is a good pattern how combine a bunch of n flows to a new transformed state given all success without error, take first error if any and start flow in loading state
y
You should use
zip
from Arrow or the
result
DSL from Arrow
j
Any sample?
y
Copy code
result {
  UiStateOf(resultA.bind(), resultB.bind(), resultC.bind(), resultD.bind())
} // Type: Result<UiState>
j
Right, so that would be go back to RxJava again 😁 I guess the easiest is create the block of asyncs in one lambda and do error handling all at once and map them. I guess like UseCase pattern.
y
I'm confused sorry. Can you provide a specific code example where you have maybe 2 of your Flow of Result? I can then show exactly what it'd look like with Arrow.
j
Not best example @Youssef Shoaib [MOD] but like:
Copy code
suspend fun fetchCombinedData(): UIState {
    return try {
        val deferredResult1 = async { NetworkRepository1.fetchData1() }
        val deferredResult2 = async { NetworkRepository2.fetchData2() }
        val deferredResult3 = async { NetworkRepository3.fetchData3() }
        val result1 = deferredResult1.await()
        val result2 = deferredResult2.await()
        val result3 = deferredResult3.await()
        UIState.Success(listOf(result1, result2, result3))
    } catch (e: Exception) {
        UIState.Error("Error: ${e.message}")
    }
}
Each async here I added to illustrate my Flow. Usually return Flow from each async here and not often combine all at once. Open for change mental model of something better. I usually have suspend at api layer and Flow in repository layer. And in my case in Compose want combine these results into one ui state data class, with loading, success and error state. Aggregate all at once.
y
Assuming that you want the flows to be
Flow<Result<Blah>>
, I'll represent them here with asyncs:
Copy code
suspend fun fetchCombinedData(): UIState = result {
  val deferred1 = async { ... }
  val deferred2 = async { ... }
  val deferred3 = async { ... }
  UIState.Success(listOf(deferred1.await().bind(), deferred2.await().bind(), deferred3.await().bind()))
}.getOrElse { UIState.Error("Error: ${e.message}") }
Coming to your original example, you can do:
Copy code
flowA.combine(flowB, flowC, flowD) { a, b, c, d ->
  result {
    UiStateOf(a.bind(), b.bind(), c.bind(), d.bind())
  }.getOrElse { UiState.Error(e.message) }
}
👌 1
j
I am not sure, but I think I want to use one try catch around all async or suspend of data and if any throws exception, return error. Else group them. And from start return loading state.
How do you get data from all those binds?
y
result
builder also does try-catch and collects any exceptions, but it also allows calling
.bind()
on
Result
values
All the binds give you back the data
j
Whats reason using Arrow for that if almost same amount of code? Does it handle more generic cases? Also how handling like Flow.onStart(loading)?
I mean what does bind really do?
Also btw what if I have mix of suspend, async and Flows I want combine? Is that possible?
y
It can handle throwing domain errors. It can also do error accumulation and such. Bind is meant to unwrap a value. For example, if your flows produce a Result, bind is a safer version of getOrThrow. You can however do it with typed errors (kinda like checked exceptions). E.g. if your data layer produces either a value, or a
DataLayerError
, you can have handling for those errors without having to make them exceptions
👍 1
I think more concrete code could help a lot, but yes it's possible. you'd
collect
or
combine
your flows, call your
suspend
funs normally, and
await
your asyncs, and then call
bind()
on any of them that might have a domain error.
j
I often struggle with many framework have result A or B. But often not tri or quad states. Like tri state = loading, error or success. And then combine all of tri states chained. Usually ends up in monsters of UseCase, transformer or some middle layer just to glue data travel from api -> domain -> ui or such. Adding one waste of data class to map between each layer. That I wonder if Arrow in general could solve reduce amount of wasteful glue classes.
I mean yeah I have a lot of code from the past I would like give as frightening sample how I dont want to do. But would like to learn if theres a better way. https://developer.android.com/kotlin/coroutines/coroutines-best-practices I dont like what Google suggest here.
y
If you're willing to accept context receivers, Arrow solves this pretty easily.
Raise<E>
allows you to throw an error of type E, so you simply would have to take in multiple
Raise
values for every instance you have. I'd suggest checking the Arrow docs btw, they go into good detail with examples
j
I looked at Arrow webpage but didnt find a single good sample do large samples. Like common having 10+ data sources into one transformed state
Do you have any sample app with large code base using Arrow? Not like basic sample app.
@Youssef Shoaib [MOD] I now found this https://cashapp.github.io/quiver/index.html Which the Outcome on top of Arrow Either. Combine this with parZip and new feature of Arrow serialization I start feeling maybe this could be worth it. Just struggle find real world sample. I guess cashapp has open source sample somewhere. They have thousand of Gradle modules I know 😁 Whats the size of using Arrow btw? I am a little hesitate to extra libs like this, because it would be scattered in entire code base. How make sure not need to fix Arrow each Kotlin or Coroutines releases etc. Regardless will check source code of parZip which looks like in general solve what I want in combination with Outcome (could be my own sealed class ofc). What I am leaning to is change so all repository for network requests always using suspend. And only for cases like Androidx datastore or paginanation flows return Flow emitting multiple results. Then will make it easier for me aggregate loading, error and success ui state transform in either my own class transformer or lambda block of suspend. Just complex in cases of Supervisor context for error handling 😁
y
Arrow is split up over many many libs, each one of them is just a few files really. Idk about exact MB but that doesn't actually matter because R8 will minify your release builds and remove any unused methods and do inlining and stuff. I believe Arrow uses mostly only stable Kotlinx coroutines APIs, hence you wouldn't need to fix anything when you upgrade the version.
thank you color 1