I am using MutableStateFlow. My flow type is sealed class with different states (Loading, Success, Error, etc). Initial value of my flow is empty:
private val _updateDepartmentsState = MutableStateFlow<DepartmentFetchState>(DepartmentFetchState.Empty)
In my repository I'm emitting different states. For example :
suspend fun updateDepartments() {
_updateDepartmentsState.emit(DepartmentFetchState.Loading)
try {
remoteDataSource.updateDepartments()
// here some code
_updateDepartmentsState.emit(DepartmentFetchState.Success(data))
} catch(e: NetworkException) {
_updateDepartmentsState.emit(DepartmentFetchState.Error)
}
}
Also in my repository I have read only flow:
val updateDepartmentsState = _updateDepartmentsState.asStateFlow()
In view model I'm collect flow via interactor. My code inside view model:
updateDepartmentsState.emitAll(
interactor
.updateState // state flow (`updateDepartmentsState` ) from repository via interactor
.map { state->
when (state) {
DepartmentFetchState.Loading -> {}
DepartmentFetchState.Error-> {}
...
}
}.also {
interactor.updateDepartments() // call updateDepartments() from repository via interator
}
As I understand from the documentation, after we have completed the collect, we must get the initial value. But it doesn't happen. Moreover, I do not receive state DepartmentFetchState.Loading. I receive only last state - DepartmentFetchState.Success.
But the most interesting thing is that if I re-call the code from the view model (for example, when updating by swipe), then I get the DepartmentFetchState.Loading state, and then the DepartmentFetchState.Success state, as expected.
I don't understand why on the first call, the initial value that I set when initializing the flow and the DepartmentFetchState.Loading state are lost.
Please, help me(
What you have described is the intended purposes of StateFlow.
Moreover, I do not receive state DepartmentFetchState.Loading. I receive only last state - DepartmentFetchState.Success.
This is because StateFlow is a state-holder observable flow that emits the current and new state updates to its collectors. And by the time you start collecting the flow your updateDepartments() has already been finished with a DepartmentFetchState.Success. Means that from this moment onward when you collect the flow, the current state is DepartmentFetchState.Success.
And then:
But the most interesting thing is that if I re-call the code from the view model (for example, when updating by swipe), then I get the DepartmentFetchState.Loading state, and then the DepartmentFetchState.Success state, as expected.
When you re-call the code you receive the result as expected, that's because you have been collecting from the flow as your updateDepartments() execute it will emit the current state DepartmentFetchState.Loading, and then DepartmentFetchState.Success respectively.
Related
I have a function in my ViewModel in which I subscribe to some updates, I want to write a test that will check that after the subscribe is triggered, the specific function is called from the subscribe.
Here is how the function looks:
fun subscribeToTablesUpdates() {
dataManager.getTablesList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { tablesList ->
updateTablesState(tablesList)
}
}
And this is the test that I wrote:
#Test
fun subscribeToTablesListTest() {
val mockedTablesList = mockk<List<Table>()
every {
viewModel.dataManager.getTablesList()
} returns Observable.just(mockedTablesList)
viewModel.subscribeToTablesUpdates()
verify {
viewModel.updateTablesState(mockedTablesList)
}
}
The issue is that I receive assertion exception without any another info and I don't know how to fix that.
Edit 1: subscribeToTableUpdates() is calling from the init block of ViewModel.
So basically the test itself was done right, but there were linking issue. Since the function of the VM was called from the init block the subscription happened only once, and that created a situation when at the time when I mocked the data service, the observer was already subscribed to the other service. Since the init block is called only once, there is no way to change the implementation of the data service to that observer.
After all this investigation the one thing which I successfully forgot came to my mind again: extract every external dependencies to constructors, so further you could substitute it for the test without any problems like this.
I have a use case where I need to trigger on a specific event collected from a flow and restart it when it closes. I also need to emit all of the events to a different flow. My current implementation looks like this:
scope.launch {
val flowToReturn = MutableSharedFlow<Event>()
while (true) {
client
.connect() // returns Flow<Event>
.catch { ... } // ignore errors
.onEach { launch { flowToReturn.emit(it) } } // problem here
.filterIsInstance<Event.Some>()
.collect { someEvent ->
doStuff(someEvent)
}
}
}.start()
The idea is to always reconnect when the client disconnects (collect then returns and a new iteration begins) while having the outer flow lifecycle separate from the inner (connection) one. It being a shared flow with potentially multiple subscribers is a secondary concern.
As the emit documentation states it is not thread-safe. Should I call it from a new coroutine then? My concern is that the emit will suspend if there are no subscribers to the outer flow and I need to run the downstream pipeline regardless.
The MutableSharedFlow.emit() documentation say that it is thread-safe. Maybe you were accidentally looking at FlowCollector.emit(), which is not thread-safe. MutableSharedFlow is a subtype of FlowCollector but promotes emit() to being thread-safe since it's not intended to be used as a Flow builder receiver like a plain FlowCollector. There's no reason to launch a coroutine just to emit to your shared flow.
There's no reason to call start() on a coroutine Job that was created with launch because launch both creates the Job and starts it.
You will need to declare flowToReturn before your launch call to be able to have it in scope to return from this outer function.
I'm building an iOS app using kotlin native and having problems with inter-thread communication.
In my app there is a class that makes an http request in a background thread (using coroutines) and needs to update the parent class state when the operation finishes. Something like this:
class Feed {
var items
fn update() {
asyncHttpRequest("http://myservice.com") { newItems ->
CoroutineScope(Dispatchers.Main).launch {
items = newItems
}
}
}
}
This fails because the feed object is frozen when passed as part of the lambda function context so it cannot be updated with the new items when the http background operation finishes.
What is the best way to design and implement something like this in kotlin-native?
Thank you!
One option would be to use atomics for modifying state concurrently:
AtomicReference
touchlab/Stately
I want to create a Kotlin coroutines Flow that emits values when
they change, and
periodically emits the last value available, every x duration since the last change or last emit.
You could create a Flow that emits at a regular interval and then just use combine. Each time you'd combine the values, you'd really just be passing along the current value of the original Flow you are interested in.
// This is the main flow you are interested in. This uses
// a Flow builder just as a simple example but this could
// be any kind of Flow, like a (Mutable)StateFlow.
val emitter = flow {
emit("Your data")
// ...
}
// This just serves as a timer.
val timer = flow {
while (currentCoroutineContext().isActive) {
emit(Unit)
delay(500)
}
}
// This will emit whenever either of the Flows emits and
// continues to do so until "emitter" stops emitting.
combine(
emitter,
timer
) { value, ticker ->
// Always just return the value of your
// main Flow.
value
}
This seems to work -- every time a new value arrives, transformLatest cancels any previous lambdas and starts a new one. So this approach emits, and then continues to emit periodically until a new value arrives.
flow.transformLatest { value ->
while(currentCoroutineContext().isActive) {
emit(value)
delay(x)
}
}
Here is an example to illustrate my confusion:
fun main() = runBlocking(Dispatchers.Default + CoroutineName("Main")) {
val broadcaster = BroadcastChannel<Int>(Channel.BUFFERED)
val flow = withContext(CoroutineName("InitialFlowCreation")) {
broadcaster.asFlow()
.map {
println("first mapping in context: $coroutineContext")
it * 10
}
.broadcastIn(CoroutineScope(Dispatchers.Default + CoroutineName("BroadcastIn")))
.asFlow()
}
val updatedFlow = withContext(CoroutineName("UpdatedFlowCreation")) {
flow.map {
println("second mapping in context: $coroutineContext")
it * 10
}
.flowOn(Dispatchers.Default + CoroutineName("FlowOn"))
}
launch(CoroutineName("Collector")) {
updatedFlow.collect {
println("Collecting $it in context: $coroutineContext")
}
}
delay(1_000)
launch(CoroutineName("OriginalBroadcast")) {
for (i in 1..10) {
broadcaster.send(i)
println("Sent original broadcast from: $coroutineContext")
delay(1_000)
}
}
return#runBlocking
}
This produces the following output (truncated):
Sent original broadcast from: [CoroutineName(OriginalBroadcast), StandaloneCoroutine{Active}#3a14b06a, DefaultDispatcher]
first mapping in context: [CoroutineName(InitialFlowCreation), UndispatchedCoroutine{Completed}#40202c08, DefaultDispatcher]
second mapping in context: [CoroutineName(UpdatedFlowCreation), UndispatchedCoroutine{Completed}#6cf04ddc, DefaultDispatcher]
Collecting 100 in context: [CoroutineName(Collector), StandaloneCoroutine{Active}#6ac9d4b5, DefaultDispatcher]
The documentation states things in various places that causes me to be confused by this result.
In Flow we have "Use channelFlow if the collection and emission of a flow are to be separated into multiple coroutines. It encapsulates all the context preservation work and allows you to focus on your domain-specific problem, rather than invariant implementation details. It is possible to use any combination of coroutine builders from within channelFlow." I know I'm not actually using the channelFlow function but a ChannelFlow is being created internally when we call broadcastIn so the same principals should apply.
I thought the first invocation of map would be run in the "OriginalBroadcast" context and the second would either be run in the "BroadcastIn" context or the "Collector" context but instead they are both run in the context where they are called. I don't understand why this is happening, shouldn't the context of map be where it is collected in order to be broadcast or the context where it is finally collected, not the context where map is called? Also the call to flowOn has no effect. What context preservation work is being encapsulated here?
Also am I correct that in a chain of flow.broadcastIn(...).asFlow().map{...}.broadcastIn(...).asFlow() the two BroadcastChannels created will not be fused? Trying to make sure I'm not missing something.
I guess what I'm really looking for is inclusive documentation of in what situation Channels are fused, how they are fused, and what context the operators that are called between ChannelFlow operators will run in.
The context preservation only applies to operations on flows, e.g. the code in flow { ... } builder works in the same context that calls collect(). The context is not preserved when operating via channels by the very nature of channels. Channels are communication primitives that are designed for communication between different coroutines.
It means that when you call broadcaster.send in one coroutine it will be received in another coroutine, in a coroutine that collects from the corresponding flow.
The documentation on channelFlow simply means that you don't have to worry about context preservation violation, which is non-trivial to ensure if you were to write such a primitive yourself.