https://kotlinlang.org logo
#coroutines
Title
# coroutines
f

frankelot

07/08/2021, 2:01 PM
Why does
.map
on a
StateFlow
returns a
Flow
?
i

iamthevoid

07/08/2021, 2:02 PM
because
.map
defined as extension to
Flow
?
f

frankelot

07/08/2021, 2:03 PM
Fair enough, but why? what If I want to
.map
a StateFlow and still keep it a StateFlow
i

iamthevoid

07/08/2021, 2:05 PM
Write your own
map
that creates new instance of
StateFlow
and return it explicitly
But imho if you do that than entropy level in the world will be increased a bit )
map usually operates onto the flow (not the
Flow
, just flow), so it must be referenced to
Flow
abstraction and returns
Flow
, but not an subtype of
Flow
I would ask you what do you do? And why do you need the same function?
f

frankelot

07/08/2021, 2:11 PM
Defenitely, thanks for replying btw.. this is what I’m doing
Copy code
class MainViewModel {

   val sf : StateFlow = ...

   fun observeFooEvents() : StateFlow = sf.map { it is Foo }
   fun observeBarEvents() : StateFlow = sf.map { it is Bar }

}
i

iamthevoid

07/08/2021, 2:13 PM
You should return
Flow
from these functions
user of
observeFooEvents
and
observeBarEvents
must don’t care about kind of abstraction in your ViewModel
It should care about emitting values
This is exactly what Flow do
f

frankelot

07/08/2021, 2:16 PM
I could, but the client the needs to use it like this
Copy code
viewModel.observeBarEvents.collectAsState(initial = ???)
it asks me for an initial state
which I already had in the first place! 😄 (the stateflow must have an initial value, but it got converted to Flow)
with StateFlow,
collectAsState
doesn’t ask for an initial state
i

iamthevoid

07/08/2021, 2:17 PM
because state flow has initial value )
f

frankelot

07/08/2021, 2:17 PM
Exactly, that’s why I want to keep it a StateFlow
because IT IS a state flow, it just makes more sense iMO
i

iamthevoid

07/08/2021, 2:18 PM
However you doesn’t need state flow in UI, because UI might emit the value to state flow
I don’t know how it calls in English, but it is bad
because you get open system at this point, but it must stay closed
f

frankelot

07/08/2021, 2:19 PM
would it? … it’s not a MutableStateFlow
that would be bad
but StateFlow won’t allow to emit
i

iamthevoid

07/08/2021, 2:21 PM
Hm. Maybe you’re correct. But i’d find a way to pass initial value from view model
Maybe with method like
Copy code
fun <T, R> StateFlow<T>.observeAsState(mapper: (T) -> R) {
    return map(mapper).observeAsState(mapper(value))
}
Mapper can be defined in view model and logic will be incapsulated inside
sf.observeAsState(vm::isFoo)
f

frankelot

07/08/2021, 2:27 PM
let me see… What is
value
in this case?
actually let me try it
i

iamthevoid

07/08/2021, 2:27 PM
value is initial value in stateflow. Is it accessible?
a

Adam Powell

07/08/2021, 2:31 PM
what is the
...
in
val sf : StateFlow = ...
?
if it's a
MutableStateFlow
or other hot data source you might be better off using snapshot state here instead of StateFlows. More conversation on this specific use case here: https://github.com/Kotlin/kotlinx.coroutines/issues/2631
l

louiscad

07/08/2021, 2:33 PM
map
returns a
Flow
no matter what because its lambda can suspend. The best it could statically do, unless you can have non suspending lambda overloads, is return a
SharedFlow
.
☝️ 1
a

Adam Powell

07/08/2021, 2:35 PM
that, plus there's a whole lot of implications of keeping each intermediate value along a chain of observable value holders
f

frankelot

07/08/2021, 2:36 PM
@iamthevoid
value
is accessible! but
collectAsState
is only available on a
Composable
context @Adam Powell
is a
MutableStateFlow
in deed. Will explore using SnapShotstate instead
i

iamthevoid

07/08/2021, 2:36 PM
You can add
@Composable
annotation to your function🤷
Anyway it will be calling from composable context
f

frankelot

07/08/2021, 2:38 PM
True! that would work… but the mapping needs to happen in the View (composable)
I’d rather have the mapping happening in the VM
i

iamthevoid

07/08/2021, 2:40 PM
You can do it inside vm, example
a

Adam Powell

07/08/2021, 2:41 PM
if the source of the data is a
MutableStateFlow
then using snapshot state instead this simplifies to:
Copy code
class MainViewModel {
    var source by mutableStateOf(...)
        private set 
    val isFoo: Boolean get() = source is Foo
    val isBar: Boolean get() = source is Bar
}
any reads of
isFoo
or
isBar
are observable. Instead of using
collectAsState
you can just write
vm.isFoo
without any special collect calls or mapping operators
(do note the
get() =
parts in that example though 🙂 )
f

frankelot

07/08/2021, 2:45 PM
@iamthevoid Could, work, the view is still too smart for my taste (knows which mapper to use). Also there’s a lot of back and forth. I’d like the view to just hook up to the VM and start listening. @Adam Powell exploring usiing
mutableStateOf
instead will see how it goes
thank you both btw 🙂
👍 2
a

Adam Powell

07/08/2021, 2:46 PM
cc @Manuel Vivo for general topic interest 🙂
f

frankelot

07/08/2021, 3:09 PM
uhm
mutableStateOf
won’t cut it for me either (I think) The example I show above is not actually what I’m doing, this is what I’m actually doing
Copy code
class MainVm {
  
   val stateFlow = ...

   val fooHandler = FooHandler(stateFlow.map { it is Foo })
   val batHandler = BarHandler(stateFlow.map { it is Bar })
}
a

Adam Powell

07/08/2021, 3:10 PM
can you post the code for FooHandler/BarHandler?
a simple lambda type might do the trick there, e.g.
() -> Boolean
, leading to
Copy code
val fooHandler = FooHandler({ state is Foo })
but what other support you might want/need depends on what else FooHandler does with it
(and more importantly, when it does it)
f

frankelot

07/08/2021, 3:15 PM
not sure it’ll help but here it is (this is BarHandler)
val filterFlow: StateFlow<Map<FILTER, FloatArray>>
Each contextual gets passed a set of filters (it’s an image editing app)
Example, the
LightContextual
gets passed down “EXPOSURE” “BRIGHTNESS”
a

Adam Powell

07/08/2021, 3:19 PM
I don't see the mappings, is that what you're trying to move into the viewmodel class?
heres the ViewModel with the Mappings
maybe not it makes more sense 😅
ignore the
stateIn
.. I was trying something out
a

Adam Powell

07/08/2021, 3:24 PM
I see
f

frankelot

07/08/2021, 3:24 PM
updated the gist*
a

Adam Powell

07/08/2021, 3:32 PM
try:
Copy code
open class SliderContextualViewModel(
    private val getFilters: () -> Map<FILTER, FloatArray>,
    val onFilterChanged: (FILTER, FloatArray) -> Unit,
    val onFilterCommitted: () -> Unit = {}
) {
    val filters: Map<FILTER, FloatArray>
        get() = getFilters()
}
then use it like
Copy code
// note: mutableStateMapOf is a snapshot state map
private var appliedFilters = mutableStateMapOf<FILTER, FloatArray>()

val lightContextualViewModel = SliderContextualViewModel(
    onFilterChanged = ::onValueChanged,
    getFilters = { appliedFilters.filterKeys { listOf(FILTER.EXPOSURE, FILTER.GRAIN, FILTER.VIBRANCE).contains(it) } },
    onFilterCommitted = history::onCommit
)
👀 1
m

Manuel Vivo

07/08/2021, 4:12 PM
I just checked the Compose Architecture doc and we should show more code in general. In particular, I think we should show a ViewModel-like class that holds the UI state using
mutableStateOf
instead of `StateFlow`/`LiveData`. If you’re not working on a hybrid screen, using the latter makes no sense to me 🙂
I’ll talk with the doc’s owner to see if we can make these improvements
🚀 4
f

frankelot

07/08/2021, 4:36 PM
@Adam Powell worked first try 🦜
thanks for the help! What kind of magic is this, I definitely need to dig deeper into whats making this work
🪄 3
if this is not a lambda, it breaks
private val getFilters: () -> Map<FILTER, FloatArray>
a

Adam Powell

07/08/2021, 4:40 PM
@Zach Klippenstein (he/him) [MOD] did a really approachable writeup on it: https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn
👀 1
f

frankelot

07/08/2021, 6:27 PM
ViewModel-like class that holds the UI state using 
mutableStateOf
 instead of `StateFlow`/`LiveData`. If you’re not working on a hybrid screen, using the latter makes no sense to me
@Manuel Vivo what do you mean by hybrid in this context?
m

Manuel Vivo

07/08/2021, 7:24 PM
Meant the case of a screen that uses both Compose and the Android View system. As you could be in a migration phase, you’d likely have a AAC ViewModel class that is in charge of handling the View logic and usually exposes a stream of UI state using
StateFlow
or
LiveData
. So if you’re mixing both approaches, you could also consume that stream for Compose. But if you’re in a Compose-only screen, you could just use the Compose State APIs.
f

frankelot

07/08/2021, 7:58 PM
Gotcha, makes sense thanks!
5 Views