Is there any way how can I wait for data from Flow and then for example log it? I have problem that code will run asynchrounously, Log.d is fired before data are available...
This is my code:
repository.getData().onEach {
state = state.copy(data = it)
}.launchIn(viewModelScope)
Log.d("test", "loadData: ${state.data}")
I think you should use coroutineBuilder :
runBlocking -> if you want to block the current thread
launch or aysnc ->if you don't want to block the current thread
and then you should calljoin method on the coroutinebuilder
example->
val job = launch{
repository.getData().onEach {
state = state.copy(data = it)
}.launchIn(viewModelScope)
}
job.join()
Log.d("test", "loadData: ${state.data}")
Related
I have an instance of CoroutineScope and log() function which look like the following:
private val scope = CoroutineScope(Dispatchers.IO)
fun log(message: String) = scope.launch { // launching a coroutine
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
And I use this test code to launch coroutines:
repeat(5) { item ->
log("Log $item")
}
The log() function can be called from any place, in any Thread, but not from a coroutine.
After a couple of tests I can see not sequential result like the following:
Log 0
Log 2
Log 4
Log 1
Log 3
There can be different order of printed logs. If I understand correctly the execution of coroutines doesn't guarantee to be sequential. What it means is that a coroutine for item 2 can be launched before the coroutine for item 0.
I want that coroutines were launched sequentially for each item and "some blocking operation" would execute sequentially, to always achieve next logs:
Log 0
Log 1
Log 2
Log 3
Log 4
Is there a way to make launching coroutines sequential? Or maybe there are other ways to achieve what I want?
Thanks in advance for any help!
One possible strategy is to use a Channel to join the launched jobs in order. You need to launch the jobs lazily so they don't start until join is called on them. trySend always succeeds when the Channel has unlimited capacity. You need to use trySend so it can be called from outside a coroutine.
private val lazyJobChannel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
scope.launch {
consumeEach { it.join() }
}
}
fun log(message: String) {
lazyJobChannel.trySend(
scope.launch(start = CoroutineStart.LAZY) {
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking operation
}
)
}
Since Flows are sequential we can use MutableSharedFlow to collect and handle data sequentially:
class Info {
// make sure replay(in case some jobs were emitted before sharedFlow is being collected and could be lost)
// and extraBufferCapacity are large enough to handle all the jobs.
// In case some jobs are lost try to increase either of the values.
private val sharedFlow = MutableSharedFlow<String>(replay = 10, extraBufferCapacity = 10)
private val scope = CoroutineScope(Dispatchers.IO)
init {
sharedFlow.onEach { message ->
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // some blocking or suspend operation
}.launchIn(scope)
}
fun log(message: String) {
sharedFlow.tryEmit(message)
}
}
fun test() {
val info = Info()
repeat(10) { item ->
info.log("Log $item")
}
}
It always prints the logs in the correct order:
Log 0
Log 1
Log 2
...
Log 9
It works for all cases, but need to be sure there are enough elements set to replay and extraBufferCapacity parameters of MutableSharedFlow to handle all items.
Another approach is
Using Dispatchers.IO.limitedParallelism(1) as a context for the CoroutineScope. It makes coroutines run sequentially if they don't contain calls to suspend functions and launched from the same Thread, e.g. Main Thread. So this solution works only with blocking (not suspend) operation inside launch coroutine builder:
private val scope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
fun log(message: String) = scope.launch { // launching a coroutine from the same Thread, e.g. Main Thread
println("$message")
TimeUnit.MILLISECONDS.sleep(100) // only blocking operation, not `suspend` operation
}
It turns out that the single thread dispatcher is a FIFO executor. So limiting the CoroutineScope execution to one thread solves the problem.
As said in title, does LiveDataScope block of code runs only one time after emit()?
Is it possible to make my LiveDataScope block of code run more than one time, because i need to make request to server, and if it fails i would like to call same code to try again.
Sample of code:
fun refreshLiveDataResource() = liveData(Dispatchers.Main){
val retriveRoutes = remoteDataSourceKt.getRoutes()
if(retriveRoutes.data != null){
routeList = retriveRoutes.data
}
emit(retriveRoutes)
}
when i call this function, after emit() if i call function again, never gets inside.
You should create a LiveData once and update its value whenever you need it instead of creating a new one everytime you want to retry. I would use a Flow instead, to update the LiveData, with a retry operator in case there is some error, like this:
val routesLiveData =
remoteDataSourceKt.getRoutes()
.onEach { retriveRoutes ->
if(retriveRoutes.data != null){
routeList = retriveRoutes.data
}
}
.retry(3) { e -> // retries up to 3 times; no argument means retrying forever
(e is IOException) // retry on any IOException but also introduce delay if retrying
.also {
if (it)
delay(1000)
}
}
.asLiveData()
You'll have to:
Make getRoutes() method return a Flow.
Make sure the Flow works on Dispatchers.IO.
Observe myLiveData from your UI.
I have a file managing class that can save a big file. The file manager class is an application singleton, so it outlives my UI classes. My Activity/Fragment can call the save suspend function of the file manager from a coroutine and then show success or failure in the UI. For example:
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
//In MyFileManager
suspend fun saveBigFile() {
//Set up the parameters
//...
withContext(Dispatchers.IO) {
//Save the file
//...
}
}
The problem with this approach is that I don't want the save operation to be aborted if the Activity is finished. If the activity is destroyed before the withContext block gets going, or if the withContext block has any suspension points in it, then saving will not be completed because the coroutine will be canceled.
What I want to happen is that the file is always saved. If the Activity is still around, then we can show UI updates on completion.
I thought one way to do it might be to start a new coroutineScope from the suspend function like this, but this scope still seems to get cancelled when its parent job is cancelled.
suspend fun saveBigFile() = coroutineScope {
//...
}
I thought another alternative might be to make this a regular function that updates some LiveData when it's finished. The Activity could observe the live data for the result, and since LiveData automatically removes lifecycle observers when they're destroyed, the Activity is not leaked to the FileManager. I'd like to avoid this pattern if the something less convoluted like the above can be done instead.
//In MyActivity:
private fun saveTheFile() {
val result = myFileManager.saveBigFile()
result.observe(this#MyActivity) {
myTextView.text = when (it) {
true -> "Successfully saved file"
else -> "Failed to save file"
}
}
}
//In MyFileManager
fun saveBigFile(): LiveData<Boolean> {
//Set up the parameters
//...
val liveData = MutableLiveData<Boolean>()
MainScope().launch {
val success = withContext(Dispatchers.IO) {
//Save the file
//...
}
liveData.value = success
}
return liveData
}
You can wrap the bit that you don't want to be cancelled with NonCancellable.
// May cancel here.
withContext(Dispatchers.IO + NonCancellable) {
// Will complete, even if cancelled.
}
// May cancel here.
If you have code whose lifetime is scoped to the lifetime of the whole application, then this is a use case for the GlobalScope. However, just saying GlobalScope.launch is not a good strategy because you could launch several concurrent file operations that may be in conflict (this depends on your app's details). The recommended way is to use a globally-scoped actor, in the role of an executor service.
Basically, you can say
#ObsoleteCoroutinesApi
val executor = GlobalScope.actor<() -> Unit>(Dispatchers.IO) {
for (task in channel) {
task()
}
}
And use it like this:
private fun saveTheFile() = lifecycleScope.launch {
executor.send {
try {
myFileManager.saveBigFile()
withContext(Main) {
myTextView.text = "Successfully saved file"
}
} catch (e: IOException) {
withContext(Main) {
myTextView.text = "Failed to save file"
}
}
}
}
Note that this is still not a great solution, it retains myTextView beyond its lifetime. Decoupling the UI notifications from the view is another topic, though.
actor is labeled as "obsolete coroutines API", but that's just an advance notice that it will be replaced with a more powerful alternative in a future version of Kotlin. It doesn't mean it's broken or unsupported.
I tried this, and it appears to do what I described that I wanted. The FileManager class has its own scope, though I suppose it could also be GlobalScope since it's a singleton class.
We launch a new job in its own scope from the coroutine. This is done from a separate function to remove any ambiguity about the scope of the job. I use async
for this other job so I can bubble up exceptions that the UI should respond to.
Then after launch, we await the async job back in the original scope. await() suspends until the job is completed and passes along any throws (in my case I want IOExceptions to bubble up for the UI to show an error message). So if the original scope is cancelled, its coroutine never waits for the result, but the launched job keeps rolling along until it completes normally. Any exceptions that we want to ensure are always handled should be handled within the async function. Otherwise, they won't bubble up if the original job is cancelled.
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
class MyFileManager private constructor(app: Application):
CoroutineScope by MainScope() {
suspend fun saveBigFile() {
//Set up the parameters
//...
val deferred = saveBigFileAsync()
deferred.await()
}
private fun saveBigFileAsync() = async(Dispatchers.IO) {
//Save the file
//...
}
}
I have some code below. Delay (3000) is just replacement for a long loop (or cycle). I’m expecting that after completion of loop println(res) will print “Some String” and then enable button. But in real life println(res) prints an empty string and button became enabled at same time when I click it.
My question is: how I can wait for end of a coroutine and only after completion of the coroutine run println(res) and button.isEnabled = true.
private var res: String = ""
private suspend fun test(): String {
delay(3000) // delay - just replacement for long loop
return "Some String" // String received after loop
}
fun onClick(view: View) {
res = ""
button.isEnabled = false
GlobalScope.launch {
res = withContext(Dispatchers.Default) {
test()
}
}
println(res) // 1. trying to get string received after loop, but not working
button.isEnabled = true // 2. button must be enabled after loop in cycle, but it's not waiting till end of loop
}
The main thing to understand here is that code within coroutine is by default executed sequentially.
I.e. coroutine is executed asynchronously in relation to "sibling" code, but code within coroutine executes synchronously by default.
For example:
fun DoSometing () {
coroutineA {
doSomethingA1()
doSomethingA2()
}
some additional code
}
Corroutine A will execute async in relation to some additional code but doSometingA2 will be executed after doSomethingA1 is done.
That means, that within a coroutine every next piece of code will be executed after the previous one is done.
So, whatever you want to execute when coroutine is done, you just put at the end of that coroutine and declare context (withContext) in which you want to execute it.
The exception is of course if you start another async piece of code within coroutine (like another coroutine).
EDIT: If you need to update UI from the coroutine, you should execute that on the main context, i.e. you'll have something like this:
GlobalScope.launch (Dispatchers.IO) {
//do some background work
...
withContext (Dispatchers.Main) {
//update the UI
button.isEnabled=true
...
}
}
You can try some thing like this:
suspend fun saveInDb() {
val value = GlobalScope.async {
delay(1000)
println("thread running on [${Thread.currentThread().name}]")
10
}
println("value = ${value.await()} thread running on [${Thread.currentThread().name}]")
}
await will wait for the coroutine to finish and then run code below it
fun onClick(view: View) {
res = ""
button.isEnabled = false
GlobalScope.launch(Dispatchers.Main){ // launches coroutine in main thread
updateUi()
}
}
suspend fun updateUi(){
val value = GlobalScope.async { // creates worker thread
res = withContext(Dispatchers.Default) {
test()
}
}
println(value.await()) //waits for workerthread to finish
button.isEnabled = true //runs on ui thread as calling function is on Dispatchers.main
}
launch is for situations where you don't care about the result outside the coroutine. To retrieve the result of a coroutine use async.
val res = GlobalScope.async(Dispatchers.Default) { test() }.await()
Note: avoid using GlobalScope, provide your own CoroutineScope instead.
why you don't move println and button.isEnabled inside GlobalScope.launch coroutine.
fun onClick(view: View) {
res = ""
button.isEnabled = false
GlobalScope.launch {
val res = withContext(Dispatchers.Default) {
test()
}
println(res)
button.isEnabled = true
}
}
if you whant your code run on main thread add Dispatchers.Main as an argument.
GlobalScope.launch(Dispatchers.Main) {
val res = withContext(Dispatchers.Default) {
test()
}
println(res)
button.isEnabled = true
}
now println and button.isEnabled run on main thread and test() fun runs on Default which in real is a worker thread.
Use the Job.join(): Unit method to wait for a coroutine to finish before continuing with current the thread:
//launch coroutine
var result = ""
val request = launch {
delay(500)
result = "Hello, world!"
}
//join coroutine with current thread
request.join()
//print "Hello, world!"
println(result)
I read a lot of docs about Kotlin coroutines but still having some doubts. I'm using Retrofit with coroutines so I need to do request with Dispatchers.IO context but use result within Dispatchers.Main context to assign it to ViewModel. My code is:
fun doHttpreq() {
viewModelScope.launch(Dispatchers.IO) {
try {
//should I call await() here? (I guess the correct way to keep execution of request outside of Main thread)
val request = RestClient.instance.getItems().await()
withContext(Dispatchers.Main) {
//or should I call await() here? (BUT need request to be executed outside of Main thread!)
if (request.isSuccessful) {
//asign items to ViewModel
} else {
//asign error to ViewModel
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
//asign error to ViewModel
}
}
}
}
You can take your deffered job in variable and then await it on your Main dispatcher like below :
try {
//Rather than await here, you take your Job as Deffered
val request: Deferred? = RestClient.instance.getItems()
withContext(Dispatchers.Main) {
//Yes, you can await here because it's non-blocking call and can be safely obtained from here once completed
val result = request?.await()
if (request.isSuccessful) {
//asign items to ViewModel
} else {
//asign error to ViewModel
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
//asign error to ViewModel
}
}
What official doc states about await() :
Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete, returning the resulting value or throwing the corresponding exception if the deferred was cancelled.
This suspending function is cancellable. If the Job of the current coroutine is cancelled or completed while this suspending function is waiting, this function immediately resumes with CancellationException.
This function can be used in select invocation with onAwait clause. Use isCompleted to check for completion of this deferred value without waiting.
As Coroutines are suspending instead of blocking, there should not be any need to manage the thread they are running on. In your case Retrofit handles this for you. Also the Deferred type is actually a hot data source. This means that the Call is executed before you even call await on it. await just waits for the data to be there.
So instead you can launch on the Main dispatcher directly. Therefore you only have one place to call await() from.
viewModelScope.launch(Dispatchers.Main) {
try {
val request = RestClient.instance.getItems().await()
if (request.isSuccessful) {
//asign items to ViewModel
} else {
//asign error to ViewModel
}
} catch (e: Exception) {
//asign error to ViewModel
}
}