How to add a delay in For loop in kotlin android - 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.

Related

how to avoid using GlobalScope

In my code, I have a time-out functionality and I want to use a countdown timer but after a lot of research, I couldn't find similar functionality as a countdown timer in Kotlin coroutine (able to start, cancel and catch finish callback). Then I decided to use GlobalScope.launch. I know this is a bad solution but my code is working perfectly.
Here is my code
viewModelScope.launch {
val timer = object: CountDownTimer(Constants.PAYMENT_TIMER, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
GlobalScope.launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
}
}
timer.start()
collectPaymentIntentUseCase.invoke(currentPaymentIntent!!).onEach { result ->
when (result) {
is Resource.Success -> {
timer.cancel()
if (result.data?.exception == null) {
My question is how can find a 100% similar function to avoid using GlobalScope but be able to use the countdown timer (start, cancel,onComplete callback)?
Note: I am using GlobalScope.lanch to be able to emit UIPaymentEvent.NavigateToBack event to my view
You don't need a CountDownTimer here. Just use the delay() suspend function.
viewModelScope.launch {
val job = launch {
delay(Constants.PAYMENT_TIMER) // Wait for timeout
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
collectPaymentIntentUseCase.invoke(currentPaymentIntent!!).onEach { result ->
when (result) {
is Resource.Success -> {
job.cancel() // Cancel the timer
if (result.data?.exception == null) {
You can use callbackFlow for listen your timer. I just code this editor. I hope it will be helpful.
fun timerFlow() = callbackFlow<UIPaymentEvent> {
val timer = object : CountDownTimer(10, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
CoroutineScope().launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
}
}
timer.start()
awaitClose()
}
Coroutines are launched inside a CoroutineScope which are similar to lifecycle for android. As such, Android automatically provide coroutine's scope for components like activity or fragment and bound them to theirs lifecycle.
While it's not recommended to use the global scope that starts and ends with android's process. There are no restriction on creating your own and limiting it to a specific view of time. Creating one starts its life and cancelling it stops all tasks inside.
In your case a countdown can be done with only coroutines. As stated in this answer.
But without changing too much of your existing code you could reuse the viewModelScope that launched your timer to emit your event.
viewModelScope.launch {
_eventFlow.emit(UIPaymentEvent.NavigateToBack)
}
Beware of the life of your scope. If the viewmodelScope is dead when the timer finish, the event will never be sent.

Job delay is not started again after canceling

So when I press a button I need to wait 3 seconds before executing another method, I worked that out with the followin
val job = CoroutineScope(Dispatchers.Main).launch(Dispatchers.Default, CoroutineStart.DEFAULT) {
delay(THREE_SECONDS)
if (this.isActive)
product?.let { listener?.removeProduct(it) }
}
override fun onRemoveProduct(product: Product) {
job.start()
}
now, if I press a cancel button right after I start the job I stop the job from happening and that is working fine
override fun onClick(v: View?) {
when(v?.id) {
R.id.dismissBtn -> {
job.cancel()
}
}
}
The problem is that when I execute again the onRemoveProduct that executes the job.start() it will not start again, seems like that job.isActive never yields to true, why is this happening ?
A Job once cancelled cannot be started again. You need to do that in a different way. One way is to create a new job everytime onRemoveProduct is called.
private var job: Job? = null
fun onRemoveProduct(product: Product) {
job = scope.launch {
delay(THREE_SECONDS)
listener?.removeProduct(product) // Assuming the two products are same. If they aren't you can modify this statement accordingly.
}
}
fun cancelRemoval() { // You can call this function from the click listener
job?.cancel()
}
Also, in this line of your code CoroutineScope(Dispatchers.Main).launch(Dispatchers.Default, CoroutineStart.DEFAULT),
You shouldn't/needn't create a new coroutine scope by yourself. You can/should use the already provided viewModelScope or lifecycleScope. They are better choices as they are lifecycle aware and get cancelled at the right time.
Dispatchers.Main is useless because it gets replaced by Dispatchers.Default anyways. Dispatchers.Default is also not required here because you aren't doing any heavy calculations (or calling some blocking code) here.
CoroutineStart.DEFAULT is the default parameter so you could have skipped that one.
And you also need not check if (this.isActive) because
If the [Job] of the current coroutine is cancelled or completed while delay is waiting, it immediately resumes with [CancellationException].

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.

How to use onLooperPrepared of HandlerThread in Android kotlin?

I am developing in Android, I want to use HandlerThread to start a countdownTimer like the following code.
private var bgHandlerThread: HandlerThread? = HandlerThread("MyHandlerThread")
private fun startTimer() {
bgHandlerThread = HandlerThread("MyHandlerThread")
bgHandlerThread!!.start()
val bgHandler = Handler(bgHandlerThread!!.looper)
bgHandler.post {
countDownTimer = object : CountDownTimer(COUNT_DOWN_MAX_TIME.toLong(), COUNT_DOWN_INTERVAL.toLong()) {
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "time:$millisUntilFinished ")
}
override fun onFinish() {
Log.d(TAG, "Timer countDown Finish ")
}
}.start()
}
}
But it show the following error
Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
com.xx.Test.startTimer
So I want to move the startTimer() to the onLooperPrepared.
In Java, it is like the following:
#Override
public void onLooperPrepared() {
}
But I did not see the method in kotlin.
Hot to use the onLooperPrepared in kotlin ?
Thanks in advance.
The onLooperPrepared() method is a protected method inside of the HandlerThread.java class, with no default implementation. If you want to use it in your code, you'd need to override it in a class that extends the HandlerThread class
class YourHandlerThread(val name = "MyHandlerThread") : HandlerThread(name) {
override fun onLoopPrepared() {...}
...
}
Not sure what are you trying to achieve, however if you want to execute something on other thread without blocking the main thread I HIGHLY recommend to start using coroutines, this is the new and recommended way to handle multi threading in kotlin.
To use them you will need to add the following dependencies to your gradle file:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
Coroutines are a big box of different chocolates so you should take your time to learn the capabilities, since it is a different mechanism compared to java threads. However for example to run your timer on a different thread is as easy as:
// it doesn't matter what thread you are currently on
CoroutineScope(Dispatchers.Default).launch {
// the code in brackets runs on a separate thread (read about dispatchers for more information) without blocking the current thread
countDownTimer = object : CountDownTimer(COUNT_DOWN_MAX_TIME.toLong(), COUNT_DOWN_INTERVAL.toLong()) {
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "time:$millisUntilFinished ")
}
override fun onFinish() {
Log.d(TAG, "Timer countDown Finish ")
}
}.start()
}
This code will work without any problems since Log allows posting from different threads, however this will not work in case when you want to update UI, since UI can be updated only from the main thread. Before coroutines this was a pain in the ass since you had to make a handler and always send/receive messages with limited capability on cancelling the currently running task. With coroutines this is as easy as:
withContext(Dispatchers.Main) {
// do your ui updates here
}
This snippet can be used inside of your coroutine to switch the context, you don't need anything more, once you switch the context to main thread you can do all the UI updates.

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.

Categories

Resources