I can launch my stateFlow collection as below
val collectingScope = CoroutineScope(Dispatchers.Default)
val stateFlow = MutableStateFlow(0)
val myJob = collectingScope.launch {
stateFlow.collect {
println("collected $it")
}
}
And I can cancel it using
myJob.cancel()
But I wonder if I can also cancel it through the stateFlow instead?
I see there's a cancel() function but it's deprecated
#Deprecated(
message = "cancel() is resolved into the extension of outer CoroutineScope which is likely to be an error." +
"Use currentCoroutineContext().cancel() instead or specify the receiver of cancel() explicitly",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("currentCoroutineContext().cancel(cause)")
)
public fun FlowCollector<*>.cancel(cause: CancellationException? = null): Unit = noImpl()
If I can do so, does the cancelation also auto-cancel myJob?
No. StateFlows cannot be cancelled. See the documentation:
State flow never completes. A call to Flow.collect on a state flow never completes normally, and neither does a coroutine started by the Flow.launchIn function. An active collector of a state flow is called a subscriber.
Related
The problem is very simple, but I can't really seem to wrap my head around it. I'm launching a non-blocking thread in the IO scope in order to read from a file. However, I can't get the result in time before I return from the method - it always returns the initial empty value "". What am I missing here?
private fun getFileContents(): String {
var result = ""
val fileName = getFilename()
val job = CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}
}
return result
}
Coroutines are launched asynchronously. Your non-suspending function cannot wait for the result without blocking. For more information about why asynchronous code results in your function returning with the default result, read the answers here.
getFileContents() has to be a suspend function to be able to return something without blocking, in which case you don't need to launch a coroutine either. But then whatever calls this function must be in a suspend function or coroutine.
private suspend fun getFileContents(): String = withContext(Dispatchers.IO) {
val fileName = getFilename()
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}.getOrDefault("")
}
There are two "worlds" of code: either you are in a suspending/coroutine context or you are not. When you are in a function that is not a suspend function, you can only return results that can be computed immediately, or you can block until the result is ready.
Generally, if you're using coroutines, you launch a coroutine at some high level in your code, and then you are free to use suspend functions everywhere because almost all of your code is initially triggered by a coroutine. By "high level", I mean you launch the coroutine when a UI screen appears or a UI button is pressed, for example.
Basically, your coroutine launches are usually in UI listeners and UI event functions, not in lower-level code like the function in your question. The coroutine calls a suspend function, which can call other suspend functions, so you don't need to launch more coroutines to perform your various sequential tasks.
The alternate solution is to return a Deferred with the result, like this:
private fun getFileContents(): Deferred<String> {
val fileName = getFilename()
return CoroutineScope(Dispatchers.IO).async {
kotlin.runCatching {
val file = getFile(fileName)
file.openFileInput().use { inputStream ->
result = String(inputStream.readBytes(), Charsets.UTF_8)
}
}.getOrDefault("")
}
}
But to unpack the result, you will need to call await() on the Deferred instance inside a coroutine somewhere.
My code stop working after call dao method, if I use GlobalScope code working, but then LiveData not update changes
override suspend fun addNewTask(title: String,priority : Int,date: Date): Boolean {
var isSuccess = false
val newTask = hashMapOf(
"title" to title,
"priority" to priority,
"task_points" to converterListOfListToJson(listOf()),
"date" to Timestamp(date)
)
val user: User? = userDao.getCurrentUser()
val newTaskList = user?.tasks?.toMutableList()
val generatedDoc = db.collection(CollectionNames.tasks).document()
val userTasks = db.collection(CollectionNames.users).document(user!!.id)
generatedDoc.set(newTask)
.addOnSuccessListener { isSuccess = true }
.addOnFailureListener { Log.e(TAG, "Error writing document") }.await()
userTasks.update(
"tasks", FieldValue.arrayUnion(generatedDoc.id)
).await()
newTaskList?.add(generatedDoc.id)
taskDao.insert(Task(generatedDoc.id, title, date, listOf(), priority.toLong())) //after code out
user.tasks = newTaskList!!.toList()
userDao.updateUser(user)
return isSuccess
}
This call of this method, addNewTask full complete but after code just end method
fun createTask(title : String, priority : String,date: Date) {
viewModelScope.launch{
tasksUseCase.addNewTask(title, priority, date)
tasksUseCase.updateTaskFromLocalDB().collect { //it is not called
_taskList.postValue(it)
}
}
}
In DAO I just use annotation update and insert
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(task: Task)
My function call in dialog fragment on click, but with viewModel of parent fragment, function deadlock without errors
.addOnSuccessListener { isSuccess = true } will change the variable to true some time in the future, when the background work is done. Your code that uses isSuccess is called before it has a chance to finish the background work.
When you used GlobalScope, it probably used the default dispatcher Dispatchers.Default, so you had a race condition and in some cases it could succeed.
When you used viewModelScope, it uses Dispatchers.Main, so there is no possibility of the result listener being called before the end of this suspend function unless there is some other suspending call in between.
What you need to do to fix it is run your task in a synchronous, suspending way instead of an asynchronous way. Since I don't know what class generatedDoc is, I can't help with that. Many libraries include extension suspend function, usually named await(), that let you get the result synchronously. If they don't provide that, you can write your own using suspendCancellableCoroutine. There are many other questions about that on here that you can search for to see how to use it.
The problem was that the viewModel was a DialogFragment which was destroyed after the method was called, due to which the ViewModelScope also ceased to exist and the method crashed in the middle
well, I have an Observable, I’ve used asFlow() to convert it but doesn’t emit.
I’m trying to migrate from Rx and Channels to Flow, so I have this function
override fun processIntents(intents: Observable<Intent>) {
intents.asFlow().shareTo(intentsFlow).launchIn(this)
}
shareTo() is an extension function which does onEach { receiver.emit(it) }, processIntents exists in a base ViewModel, and intentsFlow is a MutableSharedFlow.
fun <T> Flow<T>.shareTo(receiver: MutableSharedFlow<T>): Flow<T> {
return onEach { receiver.emit(it) }
}
I want to pass emissions coming from the intents Observable to intentsFlow, but it doesn’t work at all and the unit test keeps failing.
#Test(timeout = 4000)
fun `WHEN processIntent() with Rx subject or Observable emissions THEN intentsFlow should receive them`() {
return runBlocking {
val actual = mutableListOf<TestNumbersIntent>()
val intentSubject = PublishSubject.create<TestNumbersIntent>()
val viewModel = FlowViewModel<TestNumbersIntent, TestNumbersViewState>(
dispatcher = Dispatchers.Unconfined,
initialViewState = TestNumbersViewState()
)
viewModel.processIntents(intentSubject)
intentSubject.onNext(OneIntent)
intentSubject.onNext(TwoIntent)
intentSubject.onNext(ThreeIntent)
viewModel.intentsFlow.take(3).toList(actual)
assertEquals(3, actual.size)
assertEquals(OneIntent, actual[0])
assertEquals(TwoIntent, actual[1])
assertEquals(ThreeIntent, actual[2])
}
}
test timed out after 4000 milliseconds
org.junit.runners.model.TestTimedOutException: test timed out after
4000 milliseconds
This works
val ps = PublishSubject.create<Int>()
val mf = MutableSharedFlow<Int>()
val pf = ps.asFlow()
.onEach {
mf.emit(it)
}
launch {
pf.take(3).collect()
}
launch {
mf.take(3).collect {
println("$it") // Prints 1 2 3
}
}
launch {
yield() // Without this we suspend indefinitely
ps.onNext(1)
ps.onNext(2)
ps.onNext(3)
}
We need the take(3)s to make sure our program terminates, because MutableSharedFlow and PublishSubject -> Flow collect indefinitely.
We need the yield because we're working with a single thread and we need to give the other coroutines an opportunity to start working.
Take 2
This is much better. Doesn't use take, and cleans up after itself.
After emitting the last item, calling onComplete on the PublishSubject terminates MutableSharedFlow collection. This is a convenience, so that when this code runs it terminates completely. It is not a requirement. You can arrange your Job termination however you like.
Your code never terminating is not related to the emissions never being collected by the MutableSharedFlow. These are separate concerns. The first is due to the fact that neither a flow created from a PublishSubject, nor a MutableSharedFlow, terminates on its own. The PublishSubject flow will terminate when onComplete is called. The MutableSharedFlow will terminate when the coroutine (specifically, its Job) collecting it terminates.
The Flow constructed by PublishSubject.asFlow() drops any emissions if, at the time of the emission, collection of the Flow hasn't suspended, waiting for emissions. This introduces a race condition between being ready to collect and code that calls PublishSubject.onNext().
This, I believe, is the reason why flow collection isn't picking up the onNext emissions in your code.
It's why a yield is required right after we launch the coroutine that collects from psf.
val ps = PublishSubject.create<Int>()
val msf = MutableSharedFlow<Int>()
val psf = ps.asFlow()
.onEach {
msf.emit(it)
}
val j1 = launch {
psf.collect()
}
yield() // Use this to allow psf.collect to catch up
val j2 = launch {
msf.collect {
println("$it") // Prints 1 2 3 4
}
}
launch {
ps.onNext(1)
ps.onNext(2)
ps.onNext(3)
ps.onNext(4)
ps.onComplete()
}
j1.invokeOnCompletion { j2.cancel() }
j2.join()
In RxJava there is the valve operator that allows to pause (and buffer) a flow and resumes the flow again (and also emit the buffered values as soon as it's resumed). It's part of the rx java extensions (https://github.com/akarnokd/RxJavaExtensions/blob/3.x/src/main/java/hu/akarnokd/rxjava3/operators/FlowableValve.java).
Is there something like this for kotlin flows?
My use case is that I want to observe a flow inside an activity and never lose an event (like I would do it with LiveData e.g. which stops observing data if the activity is paused). So while the activity is paused I want the flow to buffer observed values until the activity is resumed and emit them all as soon as the activity is resumed.
So while the activity is created (until it is destroyed) I want to observe the flow BUT I only want to emit values while the activity is active and buffer the values while it is not active (but still created) until it gets active again.
Is there something to solve this or has anyone ever written something to solve this?
A combination of Lifecycle.launchWhenX and a SharedFlow should do the trick. Here's a simple example using a flow that emits a number every second.
// In your ViewModel
class MainViewModel : ViewModel() {
val numbers = flow {
var counter = 0
while (true) {
emit(counter++)
delay(1_000L)
}
}
.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily
)
}
// In your Fragment.onViewCreated()
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.numbers
.collect { number ->
Log.d("asdf", "number: $number")
}
}
This works because Lifecycle.launchWhenStarted pauses the coroutine when the Lifecycle enters a stopped state, rather than cancels it. When your Lifecycle comes back to a started state after pausing, it'll collect everything that happened while in the stopped state.
I know it is ugly solution but it works fine for me:
fun main() {
val flow = MutableSharedFlow<String>(extraBufferCapacity = 50, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val isOpened = AtomicBoolean()
val startTime = System.currentTimeMillis()
GlobalScope.launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {
flow
.transform { value ->
while (isOpened.get().not()) { }
emit(value)
}
.collect {
println("${System.currentTimeMillis() - startTime}: $it")
}
}
Thread.sleep(1000)
flow.tryEmit("First")
Thread.sleep(1000)
isOpened.set(true)
flow.tryEmit("Second")
isOpened.set(false)
Thread.sleep(1000)
isOpened.set(true)
flow.tryEmit("Third")
Thread.sleep(2000)
}
Result:
So you can set isOpened to false when your activity lifecycle paused and to true when resumed.
You can use lifecycleScope.launchWhenStarted
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow#stateflow
I have a flow :
val myflow = kotlinx.coroutines.flow.flow<Message>{}
and want to emit values with function:
override suspend fun sendMessage(chat: Chat, message: Message) {
myflow.emit(message)
}
But compiler does not allow me to do this, is there any workarounds to solve this problem?
You can use StateFlow for such use case.
Here's a sample code.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
val chatFlow = MutableStateFlow<String>("")
fun main() = runBlocking {
// Observe values
val job = launch {
chatFlow.collect {
print("$it ")
}
}
// Change values
arrayOf("Hey", "Hi", "Hello").forEach {
delay(100)
sendMessage(it)
}
delay(1000)
// Cancel running job
job.cancel()
job.join()
}
suspend fun sendMessage(message: String) {
chatFlow.value = message
}
You can test this code by running below snippet.
<iframe src="https://pl.kotl.in/DUBDfUnX3" style="width:600px;"></iframe>
The answer of Animesh Sahu is pretty much correct. You can also return a Channel as a flow (see consumeAsFlow or asFlow on a BroadcastChannel).
But there is also a thing called StateFlow currently in development by Kotlin team, which is, in part, meant to implement a similar behavior, although it is unknown when it is going to be ready.
EDIT: StateFlow and SharedFlow have been released as part of a stable API (https://blog.jetbrains.com/kotlin/2020/10/kotlinx-coroutines-1-4-0-introducing-stateflow-and-sharedflow/). These tools can and should be used when state management is required in an async execution context.
Use a SharedStateFlow it has got everything you need.
Initialization of your flow:
val myFlow = MutableSharedFlow<Message>()
and now it should just work as you were trying earlier with:
override suspend fun sendMessage(chat: Chat, message: Message) {
myFlow.emit(message)
}
Flow is self contained, once the block (lambda) inside the flow is executed the flow is over, you've to do operations inside and emit them from there.
Here is the similar github issue, says:
Afaik Flow is designed to be a self contained, replayable, cold stream, so emission from outside of it's own scope wouldn't be part of the contract. I think what you're looking for is a Channel.
And IMHO you're probably looking at the Channels, or specifically a ConflatedBroadcastChannel for multiple receivers. The difference between a normal channel and a broadcast channel is that multiple receivers can listen to a broadcast channel using openSubscription function which returns a ReceiveChannel associated with the BroadcastChannel.