Android Coroutine doesn't finish - kotlin

I have a code where I am lazily updating the UI using kotlin couroutines.
When I am putting some code inside GlobalScope.async only the first few lines are executed and the rest of the code doesn't
class MyFragment : Fragment(), CoroutineScope {
private lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
GlobalScope.async(coroutineContext) {
api.fetchOrders()
configureOrders(view!!)
// Some code here ...
}
}
For example in the above code the code after the comment doesn't get executed. And if I swap the code above the comment with the one above it then it doesn't get executed.
What am i missing ?

When you have an async method that returns something, is async and the problem is that only the first lines of the method are executed, is because you are not waiting the method to finish with the key word await.
Something needs the value returned from the function and forces the function to end, unless you use await, then it must wait the function to end.

The code is not right because your coroutines context is running on the main thread with that Dispatchers.Main keyword on the coroutine context initialization . If you want to perform API calls please change to Dispatchers.IO .
I assume api.fetchOrders() can't be run on the Main thread . Plus you need to await that response with the keyword .await() :
val yourData = api.fetchOrders().await() which will return what is inside your Deferred type . For example if it is an Deferred<ArrayList> it returns the ArrayList after you call the await() method .
Note .
If you call that await() on that coroutine context I believe you should have an error like NetworkOnMainThreadException

I found out what the issue was, the code was working but it was causing an exception which was not visible in the stack trace (not sure why).
When I changed GlobalScope.async to GlobalScope.launch it started working

A couple of things:
Your fragment is a scope but you still use GlobalScope. Dispatchers.Main + masterJob is completely ignored
If you'd use your fragmentScope, depending on the implementation of api.fetchOrders, your app could crash because you are still on the main thread
It appears you are not interested in the result, so don't use a Deferred
If async is what you want, you have to call await

Related

Correct use CoroutineScope with retrofit api call

Is this correct Coroutine and retrofit use?
i have some problems with interception of service (RuntimeException in geting getAccessTokenBlocking via intercept), and maybe it due to incorrect CoroutineScope use?
override fun onBindViewHolder(...){
val service = serviceBuilderFactory
.create(serviceEndpoint, acc)
.buildWithCache(Service::class.java)
CoroutineScope(Dispatchers.IO)
.launch(exceptionHandler) {
val obj = service.getObj()
//should i use here for webView.post - withContext(Dispatchers.Main) { ??????
// or shoud i use async and resume webView.post after callback?
webView.post {
webView.loadUrl(obj.url)
}
}
}
retrofit
#GET("/url")
suspend fun getObj(): Obj
This shouldn't be in an adapter at all. You're firing off coroutines every time an item scrolls onto the screen, and since you create a one-off CoroutineScope to launch each one, you have no means of cancelling them. If the user rotates the screen a couple of times quickly, you'll have like 30 obsolete coroutines running in the background that you can't cancel, and many of them will be doing redundant work.
Instead, you should do the fetching in a ViewModel so the fetches don't have to be repeated redundantly when the screen is rotated. And use viewModelScope to launch the coroutines so they'll be automatically cancelled when they become obsolete from the current screen going out of scope.
If you don't mind pre-fetching each item's data before showing it in the RecyclerView, you can map your data type to include the fetched URL before you even expose it to your Activity/Fragment via a LiveData or Flow.
If you want to lazily start loading the URL only when the item appears on screen, you can map your data type to a Deferred<String> using async(start = CoroutineStart.LAZY) { ... }. Then add a CoroutineScope parameter to your adapter's constructor so the Activity can pass lifecycleScope or Fragment can pass viewLifecycleScope, and you can use that scope to launch coroutines in the adapter that will automatically be cancelled when obsolete. And you can use these coroutines to await() the deferred URL.

Coroutines changing scopes

I want to do the below with coroutines but not sure what's the right way to do this and if there is another way to do this. What I want is this:
I want to create a lib that uses its own coroutine scope for requests/responses while allowing cancelling this coroutine scope when cancel method is called without cancelling the outer scope from which method of the lib is called.
For example, when a client code that has their own coroutine scope calls my lib method, I want to switch from their scope to my coroutine scope. And then, when some other coroutine/code calls cancel, I want to cancel my coroutine scope(and all it's children coroutines) without cancelling the outer scope that called the method.
Example of code:
private val myCoroutineScope = CoroutineScope(...)
suspend fun myLibMethod(){
//execute the this method in the myCoroutineScope
}
suspend fun cancel(){
myCoroutineScope.cancel()
}
For now I came up with this solution which I am not sure if it's the right one and if it's reliable:
suspend fun myLibMethod(){
withContext(myCoroutineScope.coroutineContext){
....
}
}
I wrote and tried this code which does not print Done and cancels I think the outer scope as well because of the exception:
runBlocking {
val coroutineScope = CoroutineScope(Dispatchers.Default)
println("Launching coroutine that will stop the coroutine scope after 2 seconds.")
launch(Dispatchers.Default) {
delay(2_000)
coroutineScope.cancel()
}
println("Changing coroutine context to the coroutine scope' context")
withContext(coroutineScope.coroutineContext){
delay(10_000)
}
println("Done!")
}
Actually, the above works - it does not cancel the outer scope if I catch the exception(isActive returns true). I am not still sure though if that's the best solution. I also see an example with Deferred that is cancelled when the lib's scope is cancelled(which looks better). Maybe someone has faced a similar problem and knows a better solution

Kotlin: Get Current CoroutineContext.Key without access to the CoroutineScope?

I'm trying to intercept the System.out print statements, and in a multithreaded program, I'm planning on adding these to a map using a CoroutineContext.Key as the map key, so I know which coroutine the output belongs to.
My child methods being executed don't have access to the CoroutineScope as this was kicked off on a parent method.
I was hoping for a static method along the lines of CoroutineContext.currentKey but this doesn't look like it exists.
I've achieved a similar thing in C#, using their Task.CurrentId
Is there any way for me to achieve this?
Thanks
You can create your own thread-local variable to keep your own identifier of the coroutine or even directly its saved output and use ThreadLocal.asContextElement() extension function to convert it to the coroutine context element. Now, if you start your coroutine with this element, then the specified value of this thread-local variable will be automatically installed into the corresponding thread-local variable as the this coroutine hops from thread to thread. See the following example code:
import kotlinx.coroutines.*
val myId = ThreadLocal<String>()
// I'm not a suspending function, yet I know what coroutine I work in
fun whereAmI() {
println("I'm in coroutine '${myId.get()}'")
}
fun main() = runBlocking<Unit> {
launch(myId.asContextElement("First")) {
whereAmI()
}
launch(myId.asContextElement("Second")) {
whereAmI()
}
}

How can I return an awaited value in Kotlin?

I need test() to return a player from my db. I know I can use a callback but how can I make this work with async await?
fun test(): Player {
launch(UI) {
val player = async(CommonPool) { MainActivity.database?.playerDao()!!.loadPlayer() }.await()
return player
}
}
Currently the error is return is not allowed here
In JavaScript for example I would make test async then await it's result from where it's called.
It is impossible to run a coroutine on a raw thread. At the very least you must turn an existing thread into one that spins a top-level event loop. You achieve this with a runBlocking call on the very top of the thread's call stack (i.e., inside its run() method).
On a GUI thread or any other kind of thread that runs an event loop, you need a matching Dispatcher that submits coroutines to this event loop. Kotlin already provides dispatchers for Swing, JavaFX, Android etc. In these cases you need to launch a coroutine from some existing GUI event handler, like this:
myScope.launch {
val player = test()
... use the player ...
}
myScope must be an object that implements CoroutineScope with something like this:
override val coroutineContext = Dispatchers.Main + SupervisorJob()
This will give you a way to cleanly cancel all the coroutines running within the same scope, by calling
coroutineContext[Job]!!.cancel()
My example uses the Main dispatcher, which resolves to the GUI thread when you import the Kotlin coroutines library matching your UI framework.
The test() function must become a suspend fun that temporarily switches the dispatcher to a thread pool for blocking operations. Here's how a basic example could look:
suspend fun test() = withContext(Dispatchers.IO) {
MainActivity.database?.playerDao()!!.loadPlayer()
}
Finally, note I don't mention async at all in this answer. Kotlin's async has a very specific purpose, it is not a general facility like in other languages. Its purpose is strictly parallel decomposition, where you decompose a single task into several concurrent subtasks.

How to suspend a coroutine at a specific point

I am at loss with the following problem.
I have the following code:
val parentJob: Job = Job()
launch(parent = parentJob) {
while (true)
{
if (!parentJob.isCompleted)
{
// I want to control suspension here
println("Resumed")
}
}
}
I would like to be able to control, somehow akin to a semaphore, when should the coroutine suspend and when to resume exactly in the commented part of the snippet
I know there's suspendCancellableCoroutine but I am unsure how to use it or if it is appropriate here
How can this be achieved or are there any tutorials about this?
It would be more helpful to think about coroutines in terms of callbacks and continuations, not threads and semaphores.
Internally, when the coroutine is suspended, the entire chain of suspend fun calls returns with a special internal object, COROUTINE_SUSPENDED. So the whole execution of a coroutine is just a function call during which a callback object is being built up. This callback is the continuation and when you call it, execution resumes from the place which returned the special COROUTINE_SUSPENDED object.
Kotlin runs the block you pass to suspendCancellableCoroutine with the continuation object as the parameter. So you should save this object and make it available to the code outside the coroutine.
Here's some code that may help your understanding. Note there's no need to create a separate parent job if you want to cancel the coroutine. You can just cancel the Job instance that launch returns and the coroutine will not resume after suspension.
import kotlin.coroutines.*
import kotlinx.coroutines.*
var continuation: Continuation<String>? = null
fun main(args: Array<String>) {
val job = GlobalScope.launch(Dispatchers.Unconfined) {
while (true) {
println(suspendHere())
}
}
continuation!!.resume("Resumed first time")
continuation!!.resume("Resumed second time")
job.cancel()
continuation!!.resume("This shouldn't print")
}
suspend fun suspendHere() = suspendCancellableCoroutine<String> {
continuation = it
}
If you still need an explicit check (because there aren't enough suspension points on the execution path), you can just use the isActive property which is available directly to the block:
while (isActive) ...