UI being block when viewmodel method is called - android

I'm having an issue where whenever I call this ViewModel method I got the UI blocked and the line ui_registration_done_progressBar.visibility = View.VISIBLE is not executed even though the debbuger shows me that is called normally
The sendRegistration method deals with a lot of stuff and takes some time to be finished, once it is finished the UI is unlocked and the ui_registration_done_progressBar.visibility = View.VISIBLE is executed
override fun onClick(v: View?) {
when(v?.id){
R.id.ui_registration_done_next ->{
ui_registration_done_progressBar.visibility = View.VISIBLE
sendRegistrationViewModel.sendRegistration()
}
}
}
I tried to call faster methods from ViewModel the in this case the problem does not occurs

I would recommend getting the long running task off of the UI thread. You could add use a runnable or an async task.
e.g.:
Runnable runnable = new Runnable() {
#Override
public void run() {
sendRegistrationViewModel.sendRegistration();
}
};
AsyncTask.execute(runnable);

You can use doAsync to run the task in a background thread
import org.jetbrains.anko.doAsync
...
...
override fun onClick(v: View?) {
when(v?.id){
R.id.ui_registration_done_next ->{
doAsync {
ui_registration_done_progressBar.visibility = View.VISIBLE
sendRegistrationViewModel.sendRegistration()
}
}
}
}
adding the dependency into your build.gradle file:
dependencies {
...
...
//* To use doAsync instead of AsyncTask
implementation "org.jetbrains.anko:anko-commons:0.10.4"
}

Related

Kotlin: Handler is causing my UI Thread to freeze

I want to call my function indefinitely every 1 second in a specific situation. I'm using AsyncTask to execute my API calls.
I'm using this type of call for some time but this is the first time when it actually blocked my UI Thread and I don't know why.
The handler in the code below is called inside onPostExecute.
protected fun purchaseCheck(transactionId: String){
app.sysLog("Wait for purchase...")
task = asyncTask({
api.checkPaymentStatus(transactionId)
}, taskName = "Purchase Status") { r ->
r.js?.let {
when(r.httpCode){
HTTP_PAYMENT_CHECK_PENDING -> {
App.log("purchaseCheck: response pending purchase - try again")
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
}
else -> {
App.log("purchaseCheck: response purchase success")
onPurchaseSuccessfullyCompleted()
}
}
}?:kotlin.run {
when(r.httpCode){
HTTP_PAYMENT_CARD_EXPIRED -> {
App.log("purchaseCheck: response card expired")
showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
}
else -> {
App.log("purchaseCheck: response error (retry)")
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
}
}
}
}
}
Basically
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
is causing my ProgressBar animation to freeze. When I remove that delay it is working as intended.
Here is afterDelay function:
fun afterDelay(delay: Int, body: () -> Unit): Cancellable {
class DelayRun : Runnable, Cancellable {
override fun run() = body()
override fun cancel() {
removePost(this)
}
}
return DelayRun().also {
post(delay, it)
}
}
fun removePost(runnable: Runnable) {
App.handler.removeCallbacks(runnable)
}
fun post(delay: Int, runnable: Runnable){
App.handler.postDelayed(runnable, delay.toLong())
}
Handler in Application class:
class App : Application(), Application.ActivityLifecycleCallbacks{
companion object {
val handler = Handler()
}
...
}
Edit:
After suggestion from post below I implemented Handler like this:
class App : Application(), Application.ActivityLifecycleCallbacks{
companion object {
val handler: Handler by lazy {
HandlerThread("MyHandlerThread").let {
it.start()
Handler(it.looper)
}
}
}
...
}
but it is still freezing my UI Thread. (ProgressBar is lagging)
By default, Handler posts tasks on Main (UI) thread. Therefore any job/task you send to your handler will be executed on UI thread - that is the reason why UI freezes - it waits for job to finish before redrawing.
You want to make your handler using another thread. The most simple way is to create HandlerThread.
val handlerThread = new HandlerThread("MyHandlerThread")
handlerThread.start()
val looper = handlerThread.getLooper()
val handler = new Handler(looper)
After these four lines of code, handler will execute it jobs on another thread. But let's take a problem further - you are using AsyncTask, which is deprecated. You also do not want your delay to be counted by afterDelay function, handler can do it for you.
In your case you can just do something like this:
handler.postDelayed(1000, { ... your job ... }).
Getting it together:
protected fun purchaseCheck(transactionId: String){
app.sysLog("Wait for purchase...")
val runnable = {
val status = api.checkPaymentStatus(transactionId)
status.js?.let { ... }
}
handler.postDelayed(1000, runnable)
}
I also recommend you to declare handler on Activity level rather than Application. Since you usually don't want it to be global.

How to add a delay in For loop in kotlin android

I want to animate the progress bar so i am setting its progress using for loop but the loop is too much faster that i can't see the animation . I want the code to add a delay in the loop , i tried using thread delay but not working -
here is the code
private fun showProgress() {
for(i in 0..100){
Thread{
binding.customProgressBar.progress=i
Thread.sleep(100)
}
}
}
Solution : Was not calling start method , but if there any other approach then please let me know
private fun showProgress() {
Thread {
for (i in 0..100) {
binding.customProgressBar.progress = i
Thread.sleep(100)
}
}.start()
}
now i want to terminate the thread when fragment is on pause state .
how to achieve that ?
since you are using kotlin it is better to use coroutine, you can achieve your goal with something like this:
private suspend fun someProgress(scope: CoroutineScope) {
val job = scope.launch {
for (i in 0..100) {
binding.customProgress.progress = i
delay(100)
}
}
// use job.cancel() for cancelling the job or use job.join() for waiting for the job to finish
}
you can learn more about coroutine and how it works in here.

AsyncTask as Kotlin coroutines [duplicate]

Typical use for AsyncTask: I want to run a task in another thread and after that task is done, I want to perform some operation in my UI thread, namely hiding a progress bar.
The task is to be started in TextureView.SurfaceTextureListener.onSurfaceTextureAvailable and after it finished I want to hide the progress bar. Doing this synchronously does not work because it would block the thread building the UI, leaving the screen black, not even showing the progress bar I want to hide afterwards.
So far I use this:
inner class MyTask : AsyncTask<ProgressBar, Void, ProgressBar>() {
override fun doInBackground(vararg params: ProgressBar?) : ProgressBar {
// do async
return params[0]!!
}
override fun onPostExecute(result: ProgressBar?) {
super.onPostExecute(result)
result?.visibility = View.GONE
}
}
But these classes are beyond ugly so I'd like to get rid of them.
I'd like to do this with kotlin coroutines. I've tried some variants but none of them seem to work. The one I would most likely suspect to work is this:
runBlocking {
// do async
}
progressBar.visibility = View.GONE
But this does not work properly. As I understand it, the runBlockingdoes not start a new thread, as AsyncTask would, which is what I need it to do. But using the thread coroutine, I don't see a reasonable way to get notified when it finished. Also, I can't put progressBar.visibility = View.GONE in a new thread either, because only the UI thread is allowed to make such operations.
I'm new to coroutines so I don't quite understand what I'm missing here.
To use a coroutine you need a couple of things:
Implement CoroutineScope interface.
References to Job and CoroutineContext instances.
Use suspend function modifier to suspend a coroutine without blocking the Main Thread when calling function that runs code in Background Thread.
Use withContext(Dispatchers.IO) function to run code in background thread and launch function to start a coroutine.
Usually I use a separate class for that, e.g. "Presenter" or "ViewModel":
class Presenter : CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // to run code in Main(UI) Thread
// call this method to cancel a coroutine when you don't need it anymore,
// e.g. when user closes the screen
fun cancel() {
job.cancel()
}
fun execute() = launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return#withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
With ViewModel the code is more concise using viewModelScope:
class MyViewModel : ViewModel() {
fun execute() = viewModelScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return#withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
To use viewModelScope add next line to dependencies of the app's build.gradle file:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha04"
Here is also implementation of Async Task using Kotlin coroutines and extension function on CoroutineScope.
Another approach is to create generic extension function on CoroutineScope:
fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: () -> R,
onPostExecute: (R) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
doInBackground()
}
onPostExecute(result)
}
Now we can use it with any CoroutineScope:
In ViewModel:
class MyViewModel : ViewModel() {
fun someFun() {
viewModelScope.executeAsyncTask(onPreExecute = {
// ...
}, doInBackground = {
// ...
"Result" // send data to "onPostExecute"
}, onPostExecute = {
// ... here "it" is a data returned from "doInBackground"
})
}
}
In Activity or Fragment:
lifecycleScope.executeAsyncTask(onPreExecute = {
// ...
}, doInBackground = {
// ...
"Result" // send data to "onPostExecute"
}, onPostExecute = {
// ... here "it" is a data returned from "doInBackground"
})
To use viewModelScope or lifecycleScope add next line(s) to dependencies of the app's build.gradle file:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha05".
You can get ProgressBar to run on the UI Main Thread, while using coroutine to run your task asynchronously.
Inside your override fun onCreate() method,
GlobalScope.launch(Dispatchers.Main) { // Coroutine Dispatcher confined to Main UI Thread
yourTask() // your task implementation
}
You can initialize,
private var jobStart: Job? = null
In Kotlin, var declaration means the property is mutable. If you
declare it as val, it is immutable, read-only & cannot be reassigned.
Outside the onCreate() method, yourTask() can be implemented as a suspending function, which does not block main caller thread.
When the function is suspended while waiting for the result to be returned, its running thread is unblocked for other functions to execute.
private suspend fun yourTask() = withContext(Dispatchers.Default){ // with a given coroutine context
jobStart = launch {
try{
// your task implementation
} catch (e: Exception) {
throw RuntimeException("To catch any exception thrown for yourTask", e)
}
}
}
For your progress bar, you can create a button to show the progress bar when the button is clicked.
buttonRecognize!!.setOnClickListener {
trackProgress(false)
}
Outside of onCreate(),
private fun trackProgress(isCompleted:Boolean) {
buttonRecognize?.isEnabled = isCompleted // ?. safe call
buttonRecognize!!.isEnabled // !! non-null asserted call
if(isCompleted) {
loading_progress_bar.visibility = View.GONE
} else {
loading_progress_bar.visibility = View.VISIBLE
}
}
An additional tip is to check that your coroutine is indeed running on
another thread, eg. DefaultDispatcher-worker-1,
Log.e("yourTask", "Running on thread ${Thread.currentThread().name}")
Hope this is helpful.
First, you have to run coroutine with launch(context), not with runBlocking:
https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
Second, to get the effect of onPostExecute, you have to use
Activity.runOnUiThread(Runnable)
or View.post(Runnable).
This does not use coroutines, but it's a quick solution to have a task run in background and do something on UI after that.
I'm not sure about the pros and cons of this approach compared to the others, but it works and is super easy to understand:
Thread {
// do the async Stuff
runOnUIThread {
// do the UI stuff
}
// maybe do some more stuff
}.start()
With this solution, you can easily pass values and objects between the two entities. You can also nest this indefinitely.
The following approach might be able to suffice your needs. It requires less boilerplate code and works for 100% of usecases
GlobalScope.launch {
bitmap = BitmapFactory.decodeStream(url.openStream())
}.invokeOnCompletion {
createNotification()
}
private val TAG = MainActivity::class.simpleName.toString()
private var job = Job()
//coroutine Exception
val handler = CoroutineExceptionHandler { _, exception ->
Log.d(TAG, "$exception handled !")
}
//coroutine context
val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler
//coroutine scope
private val coroutineScope = CoroutineScope(coroutineContext)
fun execute() = coroutineScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String =
withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
//delay(5000) // simulate async work
loadFileFromStorage()
return#withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
LoadingScreen.displayLoadingWithText(this,"Loading Files",false)
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
//progressDialogDialog?.dismiss()
LoadingScreen.hideLoading()
// hide progress
}
I started migrating my AsyncTask stuff in my Android Project to using coroutines...and if you just really need to do something on the UI after completing the async task (i.e., you're just overriding doInBackGround and onPostExecute in AsyncTask)...something like this can be done (i tried this myself and it works):
val job = CoroutineScope(Dispatchers.IO).async {
val rc = ...
return#async rc
}
CoroutineScope(Dispatchers.Main).launch {
val job_rc = job.await() // whatever job returns is fed to job_rc
// do UI updates here
}
The job that you have doesn't need to use the I/O Dispatcher...you can just use the default if it's not I/O intensive.
however the the coroutine waiting for the job to complete needs to be in on the Main/UI thread so you can update UI.
Yes, there's some syntax sugar that can be used to make the above code look more cool but this is at least easier to grasp when one is just starting to migrate to using coroutines.

AsyncTask as kotlin coroutine

Typical use for AsyncTask: I want to run a task in another thread and after that task is done, I want to perform some operation in my UI thread, namely hiding a progress bar.
The task is to be started in TextureView.SurfaceTextureListener.onSurfaceTextureAvailable and after it finished I want to hide the progress bar. Doing this synchronously does not work because it would block the thread building the UI, leaving the screen black, not even showing the progress bar I want to hide afterwards.
So far I use this:
inner class MyTask : AsyncTask<ProgressBar, Void, ProgressBar>() {
override fun doInBackground(vararg params: ProgressBar?) : ProgressBar {
// do async
return params[0]!!
}
override fun onPostExecute(result: ProgressBar?) {
super.onPostExecute(result)
result?.visibility = View.GONE
}
}
But these classes are beyond ugly so I'd like to get rid of them.
I'd like to do this with kotlin coroutines. I've tried some variants but none of them seem to work. The one I would most likely suspect to work is this:
runBlocking {
// do async
}
progressBar.visibility = View.GONE
But this does not work properly. As I understand it, the runBlockingdoes not start a new thread, as AsyncTask would, which is what I need it to do. But using the thread coroutine, I don't see a reasonable way to get notified when it finished. Also, I can't put progressBar.visibility = View.GONE in a new thread either, because only the UI thread is allowed to make such operations.
I'm new to coroutines so I don't quite understand what I'm missing here.
To use a coroutine you need a couple of things:
Implement CoroutineScope interface.
References to Job and CoroutineContext instances.
Use suspend function modifier to suspend a coroutine without blocking the Main Thread when calling function that runs code in Background Thread.
Use withContext(Dispatchers.IO) function to run code in background thread and launch function to start a coroutine.
Usually I use a separate class for that, e.g. "Presenter" or "ViewModel":
class Presenter : CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // to run code in Main(UI) Thread
// call this method to cancel a coroutine when you don't need it anymore,
// e.g. when user closes the screen
fun cancel() {
job.cancel()
}
fun execute() = launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return#withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
With ViewModel the code is more concise using viewModelScope:
class MyViewModel : ViewModel() {
fun execute() = viewModelScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
delay(1000) // simulate async work
return#withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
// hide progress
}
}
To use viewModelScope add next line to dependencies of the app's build.gradle file:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha04"
Here is also implementation of Async Task using Kotlin coroutines and extension function on CoroutineScope.
Another approach is to create generic extension function on CoroutineScope:
fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: () -> R,
onPostExecute: (R) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
doInBackground()
}
onPostExecute(result)
}
Now we can use it with any CoroutineScope:
In ViewModel:
class MyViewModel : ViewModel() {
fun someFun() {
viewModelScope.executeAsyncTask(onPreExecute = {
// ...
}, doInBackground = {
// ...
"Result" // send data to "onPostExecute"
}, onPostExecute = {
// ... here "it" is a data returned from "doInBackground"
})
}
}
In Activity or Fragment:
lifecycleScope.executeAsyncTask(onPreExecute = {
// ...
}, doInBackground = {
// ...
"Result" // send data to "onPostExecute"
}, onPostExecute = {
// ... here "it" is a data returned from "doInBackground"
})
To use viewModelScope or lifecycleScope add next line(s) to dependencies of the app's build.gradle file:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope
At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha05".
You can get ProgressBar to run on the UI Main Thread, while using coroutine to run your task asynchronously.
Inside your override fun onCreate() method,
GlobalScope.launch(Dispatchers.Main) { // Coroutine Dispatcher confined to Main UI Thread
yourTask() // your task implementation
}
You can initialize,
private var jobStart: Job? = null
In Kotlin, var declaration means the property is mutable. If you
declare it as val, it is immutable, read-only & cannot be reassigned.
Outside the onCreate() method, yourTask() can be implemented as a suspending function, which does not block main caller thread.
When the function is suspended while waiting for the result to be returned, its running thread is unblocked for other functions to execute.
private suspend fun yourTask() = withContext(Dispatchers.Default){ // with a given coroutine context
jobStart = launch {
try{
// your task implementation
} catch (e: Exception) {
throw RuntimeException("To catch any exception thrown for yourTask", e)
}
}
}
For your progress bar, you can create a button to show the progress bar when the button is clicked.
buttonRecognize!!.setOnClickListener {
trackProgress(false)
}
Outside of onCreate(),
private fun trackProgress(isCompleted:Boolean) {
buttonRecognize?.isEnabled = isCompleted // ?. safe call
buttonRecognize!!.isEnabled // !! non-null asserted call
if(isCompleted) {
loading_progress_bar.visibility = View.GONE
} else {
loading_progress_bar.visibility = View.VISIBLE
}
}
An additional tip is to check that your coroutine is indeed running on
another thread, eg. DefaultDispatcher-worker-1,
Log.e("yourTask", "Running on thread ${Thread.currentThread().name}")
Hope this is helpful.
First, you have to run coroutine with launch(context), not with runBlocking:
https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
Second, to get the effect of onPostExecute, you have to use
Activity.runOnUiThread(Runnable)
or View.post(Runnable).
This does not use coroutines, but it's a quick solution to have a task run in background and do something on UI after that.
I'm not sure about the pros and cons of this approach compared to the others, but it works and is super easy to understand:
Thread {
// do the async Stuff
runOnUIThread {
// do the UI stuff
}
// maybe do some more stuff
}.start()
With this solution, you can easily pass values and objects between the two entities. You can also nest this indefinitely.
The following approach might be able to suffice your needs. It requires less boilerplate code and works for 100% of usecases
GlobalScope.launch {
bitmap = BitmapFactory.decodeStream(url.openStream())
}.invokeOnCompletion {
createNotification()
}
private val TAG = MainActivity::class.simpleName.toString()
private var job = Job()
//coroutine Exception
val handler = CoroutineExceptionHandler { _, exception ->
Log.d(TAG, "$exception handled !")
}
//coroutine context
val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + handler
//coroutine scope
private val coroutineScope = CoroutineScope(coroutineContext)
fun execute() = coroutineScope.launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}
private suspend fun doInBackground(): String =
withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
//delay(5000) // simulate async work
loadFileFromStorage()
return#withContext "SomeResult"
}
// Runs on the Main(UI) Thread
private fun onPreExecute() {
LoadingScreen.displayLoadingWithText(this,"Loading Files",false)
}
// Runs on the Main(UI) Thread
private fun onPostExecute(result: String) {
//progressDialogDialog?.dismiss()
LoadingScreen.hideLoading()
// hide progress
}
I started migrating my AsyncTask stuff in my Android Project to using coroutines...and if you just really need to do something on the UI after completing the async task (i.e., you're just overriding doInBackGround and onPostExecute in AsyncTask)...something like this can be done (i tried this myself and it works):
val job = CoroutineScope(Dispatchers.IO).async {
val rc = ...
return#async rc
}
CoroutineScope(Dispatchers.Main).launch {
val job_rc = job.await() // whatever job returns is fed to job_rc
// do UI updates here
}
The job that you have doesn't need to use the I/O Dispatcher...you can just use the default if it's not I/O intensive.
however the the coroutine waiting for the job to complete needs to be in on the Main/UI thread so you can update UI.
Yes, there's some syntax sugar that can be used to make the above code look more cool but this is at least easier to grasp when one is just starting to migrate to using coroutines.

Kotlin Coroutines the right way in Android

I'm trying to update a list inside the adapter using async, I can see there is too much boilerplate.
Is it the right way to use Kotlin Coroutines?
can this be optimized more?
fun loadListOfMediaInAsync() = async(CommonPool) {
try {
//Long running task
adapter.listOfMediaItems.addAll(resources.getAllTracks())
runOnUiThread {
adapter.notifyDataSetChanged()
progress.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
runOnUiThread {progress.dismiss()}
} catch (o: OutOfMemoryError) {
o.printStackTrace()
runOnUiThread {progress.dismiss()}
}
}
After struggling with this question for days, I think the most simple and clear async-await pattern for Android activities using Kotlin is:
override fun onCreate(savedInstanceState: Bundle?) {
//...
loadDataAsync(); //"Fire-and-forget"
}
fun loadDataAsync() = async(UI) {
try {
//Turn on busy indicator.
val job = async(CommonPool) {
//We're on a background thread here.
//Execute blocking calls, such as retrofit call.execute().body() + caching.
}
job.await();
//We're back on the main thread here.
//Update UI controls such as RecyclerView adapter data.
}
catch (e: Exception) {
}
finally {
//Turn off busy indicator.
}
}
The only Gradle dependencies for coroutines are: kotlin-stdlib-jre7, kotlinx-coroutines-android.
Note: Use job.await() instead of job.join() because await() rethrows exceptions, but join() does not. If you use join() you will need to check job.isCompletedExceptionally after the job completes.
To start concurrent retrofit calls, you can do this:
val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();
Or:
val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
How to launch a coroutine
In the kotlinx.coroutines library you can start new coroutine using either launch or async function.
Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines.
The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred - a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.
Coroutine context
In Android we usually use two context:
uiContext to dispatch execution onto the Android main UI thread (for the parent coroutine).
bgContext to dispatch execution in background thread (for the child coroutines).
Example
//dispatches execution onto the Android main UI thread
private val uiContext: CoroutineContext = UI
//represents a common pool of shared threads as the coroutine dispatcher
private val bgContext: CoroutineContext = CommonPool
In following example we are going to use CommonPool for bgContext which limit the number of threads running in parallel to the value of Runtime.getRuntime.availableProcessors()-1. So if the coroutine task is scheduled, but all cores are occupied, it will be queued.
You may want to consider using newFixedThreadPoolContext or your own implementation of cached thread pool.
launch + async (execute task)
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task = async(bgContext) { dataProvider.loadData("Task") }
val result = task.await() // non ui thread, suspend until finished
view.showData(result) // ui thread
}
launch + async + async (execute two tasks sequentially)
Note: task1 and task2 are executed sequentially.
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
// non ui thread, suspend until task is finished
val result1 = async(bgContext) { dataProvider.loadData("Task 1") }.await()
// non ui thread, suspend until task is finished
val result2 = async(bgContext) { dataProvider.loadData("Task 2") }.await()
val result = "$result1 $result2" // ui thread
view.showData(result) // ui thread
}
launch + async + async (execute two tasks parallel)
Note: task1 and task2 are executed in parallel.
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task1 = async(bgContext) { dataProvider.loadData("Task 1") }
val task2 = async(bgContext) { dataProvider.loadData("Task 2") }
val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished
view.showData(result) // ui thread
}
How to cancel a coroutine
The function loadData returns a Job object which may be cancelled. When the parent coroutine is cancelled, all its children are recursively cancelled, too.
If the stopPresenting function was called while dataProvider.loadData was still in progress, the function view.showData will never be called.
var job: Job? = null
fun startPresenting() {
job = loadData()
}
fun stopPresenting() {
job?.cancel()
}
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task = async(bgContext) { dataProvider.loadData("Task") }
val result = task.await() // non ui thread, suspend until finished
view.showData(result) // ui thread
}
The complete answer is available in my article Android Coroutine Recipes
I think you can get rid of runOnUiThread { ... } by using UI context for Android applications instead of CommonPool.
The UI context is provided by the kotlinx-coroutines-android module.
We also have another option. if we use Anko library , then it looks like this
doAsync {
// Call all operation related to network or other ui blocking operations here.
uiThread {
// perform all ui related operation here
}
}
Add dependency for Anko in your app gradle like this.
implementation "org.jetbrains.anko:anko:0.10.5"
Like sdeff said, if you use the UI context, the code inside that coroutine will run on UI thread by default. And, if you need to run an instruction on another thread you can use run(CommonPool) {}
Furthermore, if you don't need to return nothing from the method, you can use the function launch(UI) instead of async(UI) (the former will return a Job and the latter a Deferred<Unit>).
An example could be:
fun loadListOfMediaInAsync() = launch(UI) {
try {
withContext(CommonPool) { //The coroutine is suspended until run() ends
adapter.listOfMediaItems.addAll(resources.getAllTracks())
}
adapter.notifyDataSetChanged()
} catch(e: Exception) {
e.printStackTrace()
} catch(o: OutOfMemoryError) {
o.printStackTrace()
} finally {
progress.dismiss()
}
}
If you need more help I recommend you to read the main guide of kotlinx.coroutines and, in addition, the guide of coroutines + UI
If you want to return some thing from background thread use async
launch(UI) {
val result = async(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
view.setText(result)
}
If background thread is not returning anything
launch(UI) {
launch(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
}
All the above answers are right, but I was having a hard time finding the right import for the UI from kotlinx.coroutines, it was conflicting with UI from Anko.
Its
import kotlinx.coroutines.experimental.android.UI
Here's the right way to use Kotlin Coroutines. Coroutine scope simply suspends the current coroutine until all child coroutines have finished their execution. This example explicitly shows us how child coroutine works within parent coroutine.
An example with explanations:
fun main() = blockingMethod { // coroutine scope
launch {
delay(2000L) // suspends the current coroutine for 2 seconds
println("Tasks from some blockingMethod")
}
coroutineScope { // creates a new coroutine scope
launch {
delay(3000L) // suspends this coroutine for 3 seconds
println("Task from nested launch")
}
delay(1000L)
println("Task from coroutine scope") // this line will be printed before nested launch
}
println("Coroutine scope is over") // but this line isn't printed until nested launch completes
}
Hope this helps.
Please find attached the implementation for a remote API call with Kotlin Coroutines & Retrofit library.
import android.view.View
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.test.nyt_most_viewed.NYTApp
import com.test.nyt_most_viewed.data.local.PreferenceHelper
import com.test.nyt_most_viewed.data.model.NytAPI
import com.test.nyt_most_viewed.data.model.response.reviews.ResultsItem
import kotlinx.coroutines.*
import javax.inject.Inject
class MoviesReviewViewModel #Inject constructor(
private val nytAPI: NytAPI,
private val nytApp: NYTApp,
appPreference: PreferenceHelper
) : ViewModel() {
val moviesReviewsResponse: MutableLiveData<List<ResultsItem>> = MutableLiveData()
val message: MutableLiveData<String> = MutableLiveData()
val loaderProgressVisibility: MutableLiveData<Int> = MutableLiveData()
val coroutineJobs = mutableListOf<Job>()
override fun onCleared() {
super.onCleared()
coroutineJobs.forEach {
it.cancel()
}
}
// You will call this method from your activity/Fragment
fun getMoviesReviewWithCoroutine() {
viewModelScope.launch(Dispatchers.Main + handler) {
// Update your UI
showLoadingUI()
val deferredResult = async(Dispatchers.IO) {
return#async nytAPI.getMoviesReviewWithCoroutine("full-time")
}
val moviesReviewsResponse = deferredResult.await()
this#MoviesReviewViewModel.moviesReviewsResponse.value = moviesReviewsResponse.results
// Update your UI
resetLoadingUI()
}
}
val handler = CoroutineExceptionHandler { _, exception ->
onMoviesReviewFailure(exception)
}
/*Handle failure case*/
private fun onMoviesReviewFailure(throwable: Throwable) {
resetLoadingUI()
Log.d("MOVIES-REVIEWS-ERROR", throwable.toString())
}
private fun showLoadingUI() {
setLoaderVisibility(View.VISIBLE)
setMessage(STATES.INITIALIZED)
}
private fun resetLoadingUI() {
setMessage(STATES.DONE)
setLoaderVisibility(View.GONE)
}
private fun setMessage(states: STATES) {
message.value = states.name
}
private fun setLoaderVisibility(visibility: Int) {
loaderProgressVisibility.value = visibility
}
enum class STATES {
INITIALIZED,
DONE
}
}

Categories

Resources