I'm writting my first app in Kotlin, so I'm pretty new to this. One of the functions it performs is to read something from an API, and then update the screen based on the result.
I have tried lots of things with the coroutines, but nothing seems to work.
For example, I have something like this:
private fun readAPI() {
runBlocking {
fun rAPI() = async {
val api = "..."
result = URL(api).readText()
}
println(tag, "Result: " + rAPI().await())
}
}
And lots of different approaches. Nothing seems to work. In the above case I'm getting an exception "android.os.NetworkOnMainThreadException".
The only thig that has worked so far, is something using OkHttp3 as described here: https://rstopup.com/como-hacer-una-solicitud-a-la-api-de-kotlin.html (it's in Spanish, but you'll get the idea), and this works, it brings the API response, I parse it, fill in my sqlite3 database and so on. But, since I don't know when the API ends, I can't update the screen controls. And if I try to do it serially, I get an exception that only the thread which started the activity is the one that can update the activity or something like that.
I've seen, and follow LOTS of tutorials that talks about suspend functions, launch, etc., and they try to mimick an API call with delay()
, those tutorials work perfectly, until I try to do a real API call.
So, can you point me to a full example on how to call an API with Kotlin, and then update some screen elements?
EDIT I'm editing changing the fun by val:
runBlocking {
val rAPI = async {
val api = "..."
URL(api).readText()
}
Log.w(tag, rAPI.await())
}
I got the "android.os.NetworkOnMainThreadException" exception.
async-await Kotlin's async function allows running concurrent coroutines and returns a Deferred<T> result. Deferred is a non-blocking cancellable future to act as a proxy for a result that is initially unknown. For example, by calling Deferred#wait method, we wait until the task is done, and then we have the result.
To wait for a coroutine to finish, you can call Job. join . join is a suspending function, meaning that the coroutine calling it will be suspended until it is told to resume. At the point of suspension, the executing thread is released to any other available coroutines (that are sharing that thread or thread pool).
In order to migrate to the async/await pattern, you have to return the async() result from your code, and call await() on the Deferred , from within another coroutine. By doing so, you can remove callbacks you used to use, to consume asynchronously provided values.
We can wait for the coroutine to finish by calling join() on the Job. For example, suppose we have a suspend function to download some files. We can launch this coroutine and capture the resulting job, which we can later use to join — to wait for the operation to complete.
Since you want to use coroutine
-async
ways, you must tell main thread to waiting for you. you need to use suspend
function or block to do this.
GlobalScope.launch {
suspend {
Log.d("coroutineScope", "#runs on ${Thread.currentThread().name}")
delay(10000)
withContext(Dispatchers.Main) {
Log.d("coroutineScope", "#runs on ${Thread.currentThread().name}")
}
}.invoke()
}
result log
09:36:09.500 D/: #runs on DefaultDispatcher-worker-1
// 10 seconds later
09:36:19.678 D/: #runs on main
I think this should do the trick.
However I suggest you to understand how to use OkHttp/Volley by passing callbacks(with onSuccess and onFail or something) into that.
Or Retrofit2
with RxJava
to handle many of these issues.
EDIT
For the Module with the Main dispatcher had failed to initialize
error, replace the withContext()
line with this
withContext(Handler(Looper.getMainLooper()).asCoroutineDispatcher())
EDIT Now don't use RxJava, use liveData/LiveEvent to implement the observer pattern
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With