That is just like Rx, where map, flatMap, filter, ...
# coroutines
s
That is just like Rx, where map, flatMap, filter, etc return a plain Observable when called on a (Behavior)Subject. You need to keep a reference to the original/source (Mutable)StateFlow to get (or set) its value.
o
Could you elaborate? StateFlow is often compared to Android's LiveData, which had a .map {} method.
liveData.map {}
How does one let's say a
StateFlow<Int>
that maps it and multiplies the int value with 2, and creates a new StateFlow from it?
s
It that map use case of LiveData, do you use liveData.value map result or consume it as LiveData? I guess you don't want to change value of a mapped stream. In that case returning stateflow as result of map is useless
o
Which usecase are you talking about?
s
A
StateFlow
is a
Flow
. But map, flatMapXxx and such return a
Flow
. A
StateFlow
is the root source of a reactive stream, it contains the source, the state, whose changes are emitted downstream. A
StateFlow
is like an Rx
BehaviorSubject
this way.
o
I never used Rx FYI😆
s
why do you need stateflow after map operation? Do you need to use StateFlow.value? If not flow should be enough
o
Yes, I need to use StateFlow.value
The mapped Flow still represents a State, so it should be StateFlow
u
you need to pipe it into a another stateflow instance if you care about the result
o
Copy code
private val chatContacts: StateFlow<List<ChatContact>> = MutableStateFlow(emptyList<ChatContact>())
        .also { mutableFlow ->
            viewModelEngineScope.launch {
                Napier.d("seeChatContactsUseCase(): begin")
                seeChatContactsUseCase().let { seeChatContactsResult ->
                    Napier.d("seeChatContactsUseCase(): end")
                    Napier.d("seeChatContactsResult: $seeChatContactsResult")
                    when (seeChatContactsResult) {
                        is Result.Failure -> _errors.offer(seeChatContactsResult.exception.toString())
                        is Result.Success -> mutableFlow.value = seeChatContactsResult.data
                    }
                }
            }
        }

    override val chatContactRows: StateFlow<List<ChatContactRow>> = MutableStateFlow(emptyList<ChatContactRow>())
        .also { mutableFlow ->
            viewModelEngineScope.launch {
                chatContacts.collect { chatContacts ->
                    mutableFlow.value = chatContacts.map { chatContact ->
                        ChatContactRow(
                            id = chatContact.familyMembershipId,
                            title = chatContact.name,
                            avatar = chatContact.picture
                                ?: ImageSource.Url("<https://cdn.pixabay.com/photo/2016/08/08/09/17/avatar-1577909_960_720.png>")
                        )
                    }
                }
            }
        }
You are talking about something like that, right?
u
bit hard to follow
o
Copy code
override val chatContactRows: StateFlow<List<ChatContactRow>> = MutableStateFlow(emptyList<ChatContactRow>())
        .also { mutableFlow ->
            viewModelEngineScope.launch {
                chatContacts.collect { chatContacts ->
                    mutableFlow.value = chatContacts.map { chatContact ->
                        ChatContactRow(...)
                        )
                    }
                }
            }
        }
u
I dont use Flow yet, but do Rx, Id just subscibe to the flow and pipe into the other instance
o
Basically doing the above
It works, but is less declarative then a simple map function, like LiveData has.
So you can do the following:
Copy code
val name: StateFlow<String> = MutableStateFlow("Eric")
val greeting: StateFlow<String> = name.map { name -> "Hi $name!" }
s
Try to design greeting like you won't need to use stateflow.value.
u
not sure why are ypu dong the mapping at collect level, just apply operators and when youre done assign it to the other one (in collect)
o
Why would I need to do that @Sinan Kozak? The greeting is a state, I should be able to get it whenever I want.
s
Or if stateflow doesn't suit the usecase go with LiveData. You example could be simple liveData builder
o
@ursus could you elaborate?
@Sinan Kozak I'm not writing Android
s
You can always create state flows as separate objects and launch your Coroutines outside of also block and map the values in onEach than set to greetings.
o
I'm doing a similar approach in a example above. It works, but it is not as declarative and readable as the greeting example.
👍 1
s
You can extract logic as stateFlow builder to reuse (liveData builder could be used as example, i can share if you want)
o
Yes I could make a utility thing for my own, and maybe I will, but I think something like this is a very common usecase, and should be in the coroutines library
👍 1
u
in rx it would look something like this
Copy code
init {
	inputRelay
		.map { }
		.subscribe {
			outputRelay.accept(it)
		}
}
👍 1
o
I never used Rx, so your example doesn't help me much
u
relay = mutableflow; subscribe = collect
s
If your in a suspending context (in a Coroutine), you can call
.first()
on the Flow you have (which is backed by a StateFlow).
u
and yea I wanted to add that, pulling a value from outside the object is a smell, and if you really need it, im sure flow has blocking operators to do that; rx
blockingFirst()
o
If pulling a value from outside the object is a smell, then wha'ts the point of StateFlow? Only thing StateFlow adds to its interface is a
val value: T
s
Note that this (calling first() or bkockinGet()) may be problematic not you have a
.filter
operation somewhere in the middle of the chain....
o
@streetsofboston that's nice to know, but one would like to get the value of state in a non suspending env
u
I personally would not expose a Relay/StateFlow as a public interface, always Observable/Flow
☝️ 1
s
You often use a MutableStateFlow as the producer of values. It holds the state. All the Flows that are returned by map, flatMap, filter, etc just modify the stream of emitted values and don't hold a (modified) copy of the state.
o
Then there might be misunderstanding in the use of StateFlow, because the KDoc's example exposes a StateFlow as well:
Copy code
class CounterModel {
    private val _counter = MutableStateFlow(0) // private mutable state flow
    val counter: StateFlow<Int> get() = _counter // publicly exposed as read-only state flow

    fun inc() {
        _counter.value++
    }
}
A ViewModel exposing StateFlow's makes sense for me. View should always have access to the current viewmodel state
u
well, I wouldn't, guessing its read only so not as bad
s
What would happen if
filter
would return a
StateFlow
....? Filtered out values may cause the returned
StateFlow
to not have a current state....
u
but its a smell regardless, only keep it for some weird blocking apis like android services and what not, receiving a push message is one I remember
-- view should subscribe to the viewmodel.state
man I hate flow changed subscribe to collect, subscribe is much better semantically
s
View should not have access to the current state. The View should subscribe/collect and receive the current state (again).
o
@streetsofboston Didnt think filter through, lets stick with map now
@streetsofboston that can be a different discussion, the idea is that StateFlow represents a state
s
And it's not only filter... flatMapXxx may cause this situation as well, any custom operator the user defines, etc.
o
Whats the point of StateFlow then? Then only MutableStateFlow would have been enougj
👌 1
u
I thought the same
s
Of you need access to the state, keep hold of the reference to that original StateFlow and call
.value
on it.
u
My guess is they wanted to mirror the readonly/mutable pairs of collections
o
Yes, so back to my greeting example
If you want to describe a mapping a state from another state, map should be the way to do it, instead of imperatively collecting the source stateflow and assigning it to the new mutable flow
u
sure, I'd argue its the same just sugared but yes
s
That would mean you'd need to store two copies of basically the same state, one the original one, the other one the mapped value of it.
You can add a MutableStateFlow from the final Flow returned from your map or other functions, eg by changing its value in the Flow's onEach.
But in reactive programming, like using Flows or Observables (Rx), you will never retrieve (pull) the state/values, you receive (getting pushed) them.