https://kotlinlang.org logo
Title
s

streetsofboston

05/10/2020, 5:19 PM
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

Orhan Tozan

05/10/2020, 5:22 PM
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

Sinan Kozak

05/10/2020, 5:33 PM
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

Orhan Tozan

05/10/2020, 5:34 PM
Which usecase are you talking about?
s

streetsofboston

05/10/2020, 6:03 PM
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

Orhan Tozan

05/10/2020, 6:03 PM
I never used Rx FYI😆
s

Sinan Kozak

05/10/2020, 6:13 PM
why do you need stateflow after map operation? Do you need to use StateFlow.value? If not flow should be enough
o

Orhan Tozan

05/10/2020, 6:14 PM
Yes, I need to use StateFlow.value
The mapped Flow still represents a State, so it should be StateFlow
u

ursus

05/10/2020, 6:23 PM
you need to pipe it into a another stateflow instance if you care about the result
o

Orhan Tozan

05/10/2020, 6:24 PM
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

ursus

05/10/2020, 6:26 PM
bit hard to follow
o

Orhan Tozan

05/10/2020, 6:27 PM
override val chatContactRows: StateFlow<List<ChatContactRow>> = MutableStateFlow(emptyList<ChatContactRow>())
        .also { mutableFlow ->
            viewModelEngineScope.launch {
                chatContacts.collect { chatContacts ->
                    mutableFlow.value = chatContacts.map { chatContact ->
                        ChatContactRow(...)
                        )
                    }
                }
            }
        }
u

ursus

05/10/2020, 6:27 PM
I dont use Flow yet, but do Rx, Id just subscibe to the flow and pipe into the other instance
o

Orhan Tozan

05/10/2020, 6:27 PM
Basically doing the above
It works, but is less declarative then a simple map function, like LiveData has.
So you can do the following:
val name: StateFlow<String> = MutableStateFlow("Eric")
val greeting: StateFlow<String> = name.map { name -> "Hi $name!" }
s

Sinan Kozak

05/10/2020, 6:29 PM
Try to design greeting like you won't need to use stateflow.value.
u

ursus

05/10/2020, 6:29 PM
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

Orhan Tozan

05/10/2020, 6:29 PM
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

Sinan Kozak

05/10/2020, 6:30 PM
Or if stateflow doesn't suit the usecase go with LiveData. You example could be simple liveData builder
o

Orhan Tozan

05/10/2020, 6:30 PM
@ursus could you elaborate?
@Sinan Kozak I'm not writing Android
s

Sinan Kozak

05/10/2020, 6:32 PM
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

Orhan Tozan

05/10/2020, 6:32 PM
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

Sinan Kozak

05/10/2020, 6:33 PM
You can extract logic as stateFlow builder to reuse (liveData builder could be used as example, i can share if you want)
o

Orhan Tozan

05/10/2020, 6:34 PM
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

ursus

05/10/2020, 6:39 PM
in rx it would look something like this
init {
	inputRelay
		.map { }
		.subscribe {
			outputRelay.accept(it)
		}
}
👍 1
o

Orhan Tozan

05/10/2020, 6:41 PM
I never used Rx, so your example doesn't help me much
u

ursus

05/10/2020, 6:53 PM
relay = mutableflow; subscribe = collect
s

streetsofboston

05/10/2020, 6:54 PM
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

ursus

05/10/2020, 6:55 PM
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

Orhan Tozan

05/10/2020, 6:56 PM
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

streetsofboston

05/10/2020, 6:56 PM
Note that this (calling first() or bkockinGet()) may be problematic not you have a
.filter
operation somewhere in the middle of the chain....
o

Orhan Tozan

05/10/2020, 6:57 PM
@streetsofboston that's nice to know, but one would like to get the value of state in a non suspending env
u

ursus

05/10/2020, 6:58 PM
I personally would not expose a Relay/StateFlow as a public interface, always Observable/Flow
☝️ 1
s

streetsofboston

05/10/2020, 6:59 PM
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

Orhan Tozan

05/10/2020, 6:59 PM
Then there might be misunderstanding in the use of StateFlow, because the KDoc's example exposes a StateFlow as well:
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

ursus

05/10/2020, 7:01 PM
well, I wouldn't, guessing its read only so not as bad
s

streetsofboston

05/10/2020, 7:01 PM
What would happen if
filter
would return a
StateFlow
....? Filtered out values may cause the returned
StateFlow
to not have a current state....
u

ursus

05/10/2020, 7:01 PM
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

streetsofboston

05/10/2020, 7:02 PM
View should not have access to the current state. The View should subscribe/collect and receive the current state (again).
o

Orhan Tozan

05/10/2020, 7:04 PM
@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

streetsofboston

05/10/2020, 7:06 PM
And it's not only filter... flatMapXxx may cause this situation as well, any custom operator the user defines, etc.
o

Orhan Tozan

05/10/2020, 7:07 PM
Whats the point of StateFlow then? Then only MutableStateFlow would have been enougj
:yes: 1
u

ursus

05/10/2020, 7:07 PM
I thought the same
s

streetsofboston

05/10/2020, 7:07 PM
Of you need access to the state, keep hold of the reference to that original StateFlow and call
.value
on it.
u

ursus

05/10/2020, 7:08 PM
My guess is they wanted to mirror the readonly/mutable pairs of collections
o

Orhan Tozan

05/10/2020, 7:08 PM
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

ursus

05/10/2020, 7:12 PM
sure, I'd argue its the same just sugared but yes
s

streetsofboston

05/10/2020, 7:13 PM
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.