Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 472 Vote(s) - 3.51 Average
  • 1
  • 2
  • 3
  • 4
  • 5
AsyncTask as kotlin coroutine

#1
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 `runBlocking`does 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.
Reply

#2
First, you have to run coroutine with `launch(context)`, not with `runBlocking`:
[

[To see links please register here]

][1]

Second, to get the effect of `onPostExecute`, you have to use

[Activity.runOnUiThread(Runnable)][2]
or [View.post(Runnable)][3].


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

Reply

#3
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.
Reply

#4
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.
Reply

#5
Another approach is to create generic extension function on `CoroutineScope`:

fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: () -> R,
onPostExecute: ® -> 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`][1]:

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"`.

[1]:

[To see links please register here]

Reply

#6
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()
}
Reply

#7
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
}




Reply

#8
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`][1] 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`][2].


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#9
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):

```kotlin
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.
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through