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.
Related
This two code run exactly same. What is different putting Job in CoroutineScope and launch?
private val job = CoroutineScope(Dispatchers.Main).launch(start = CoroutineStart.LAZY) {
for(i in 10 downTo 0) {
Log.d("test", ": $i")
delay(1000)
}
}
CoroutineScope(Dispatchers.Main+job).launch{ }
CoroutineScope(Dispatchers.Main).launch(job) { }
Technically, both result in the same behavior, but the main point is that neither is a good way to use the CoroutineScope() factory. This is the idiomatic way to write the same thing:
GlobalScope.launch(Dispatchers.Main+job) { ... }
If this raises your eyebrows ("Don't use GlobalScope!"), that's because it should — your examples are just another way to make the same mistake, with more verbose code. You construct a CoroutineScope without holding a reference to it, resulting in exactly the same unbounded and non-cancellable scope as the GlobalScope singleton.
In addition, the way you use a Job instance that is a handle to an actual coroutine, is also wrong: the job associated with a coroutine scope should be a standalone instance returned from either Job() or SupervisorJob(). Its only purpose is to serve as the central point from which to cancel the entire scope or inspect its state.
It doesn't seem like there are many differences between the two, looking at the source code. The operator fun plus documentation states
Returns a context containing elements from this context and elements
from other context. The elements from this context with the same key
as in the other one are dropped.
which explains how your first test works. For the second, calling launch with a context parameter, calls into CoroutineScope.newCoroutineContext
Creates a context for the new coroutine. It installs
Dispatchers.Default when no other dispatcher or
ContinuationInterceptor is specified, and adds optional support for
debugging facilities (when turned on).
and looking at the source code of it:
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
We can see it also ends up using the operator fun plus.
There are some other differences that seem negligable to mention here, but ultimately the context of the launched coroutine looks to be the same in both of your tests.
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
I'm new to Kotlin/Coroutines and I've noticed two different ways to use CoroutineScope.
Option 1 is as follows, within any function:
CoroutineScope(Dispatchers.Default).launch {
expensiveOperation()
}
Option 2 is by implementing the CoroutineScope interface in your class, overriding the CoroutineContext, and then you can just launch coroutines easily with launch or async:
#Service
class ServiceImpl() : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default + Job()
fun someFunction() {
launch {
expensiveOperation()
}
}
}
I am currently developing a backend endpoint that will do the following:
take a request
save the request context to a database
launch a non blocking coroutine in the background to perform an expensive/lengthy operation on the request, and immediately return an http 200. (essentially, once we have the context saved, we can return a response and let the request process in the background)
What is the difference in the two use cases, and for this scenario, which is the preferred method for obtaining a CoroutineScope?
This endpoint may receive multiple requests per second, and the lengthy operation will take a minute or two, so there will definitely be multiple requests processing at the same time, originating from various requests.
Also, if it's option 2, do I want to pass the scope/context to the function that does the heavy processing? Or is that unnecessary? For example:
class ServiceImpl() : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default + Job()
fun someFunction() {
launch {
expensiveOperation(CoroutineScope(coroutineContext))
}
}
private fun expensiveOperation(scope: CoroutineScope)
{
// perform expensive operation
}
}
This is a Spring Boot app, and I'm using version 1.3 of Kotlin.
Please let me know if you have any thoughts/suggestions on how to best structure this service class. Thanks
I would recommend option 2. It will give you chance to clearly define parent Job for all of your coroutines. That gives a chance to shut down the whole execution correctly too.
There are several more coroutine context keys to include - CoroutineName, CoroutineExceptionHandler and so one.
Lastly, the structural concurrency may work better if you pass the CoroutineScope and the associated Job explicitly.
https://medium.com/#elizarov/structured-concurrency-722d765aa952
Also, take a look the explanation on that from Roman:
https://medium.com/#elizarov/coroutine-context-and-scope-c8b255d59055
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()
}
}
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.