How to use onLooperPrepared of HandlerThread in Android kotlin? - android

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.

Related

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.

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.

Why does viewModelScope.launch run on the main thread by default

While I was learning coroutines and how to properly use them in an android app I found something I was surprised about.
When launching a coroutine using viewModelScope.launch { } and setting a breakpoint inside the launch lambda I noticed my app wasn't responsive anymore because it was still on the main thread.
This confuses me because the docs of viewModelScope.launch { } clearly state:
Launches a new coroutine without blocking the current thread
Isn't the current thread the main thread ? What is the whole purpose of launch if it doesn't run on a different thread by default ?
I was able to run it on anther thread using viewModelScope.launch(Dispatchers.IO){ } which works as I was expecting, namely on another thread.
What I am trying to accomplish from the launch method is to call a repository and do some IO work namely call a webservice and store the data in a room db. So I was thinking of calling viewModelScope.launch(Dispatchers.IO){ } do all the work on a different thread and in the end update the LiveData result.
viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}
So my second question is, is this the way to go ?
ViewModelScope.launch { } runs on the main thread, but also gives you the option to run other dispatchers, so you can have UI & Background operations running synchronously.
For you example:
fun thisWillRunOnMainThread() {
viewModelScope.launch {
//below code will run on UI thread.
showLoadingOnUI()
//using withContext() you can run a block of code on different dispatcher
val result = novel.id = withContext(Dispatchers.IO) {
withsomeRepository.someWork()
}
//The below code waits until the above block is executed and the result is set.
liveData.value = result
finishLoadingOnUI()
}
}
For more reference, I would say there are some neat articles that will help you understand this concept.
Medium link that explains it really neat.
So my second question is, is this the way to go ?
I would expect two things to be different in your current approach.
1.) First step would be to define the scheduler of the background operation via withContext.
class SomeRepository {
suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
...
}
}
This way, the operation itself runs on a background thread, but you didn't force your original scope to be "off-thread".
2.) Jetpack Lifecycle KTX provides the liveData { coroutine builder so that you don't have to postValue to it manually.
val liveData: LiveData<SomeResult> = liveData {
emit(someRepository.someWork())
}
Which in a ViewModel, you would use like so:
val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
withContext(Dispatchers.IO) {
emit(someRepository.someWork())
}
}
And now you can automatically trigger data-loading via observing, and not having to manually invoke viewModelScope.launch {}.
The idea behind main thread being default is you can run UI operations without having to change the context. It is a convention I guess Kotlin coroutine library writers have chosen
Suppose if by default if the launch runs on IO thread then the code would look like this
viewmodelScope.launch {
val response = networkRequest()
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Suppose if by default if the launch runs on Default thread then the code would look like this
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Since the default launch is on main thread, now you have to do below
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
renderUI(response)
}
To avoid the messy code initializing the response with null, we can also make the networkRequest as suspend and wrap the business logic of networkRequest() function in withContext(Dispatchers.IO) and that's how lot of people write their networkRequest() function as well! Hope this makes sense
One of the main reasons it runs on Main thread, is because it's more practical for general use in ViewModel, like murali kurapati wrote. It was a design choice.
It's also important to note that all suspending functions should be "main safe" according to best pracices. That means, that your repository should switch coroutine context accordingly, like so:
class someRepository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun someWork() {
withContext(ioDispatcher) {
TODO("Heavy lifting")
}
}
}

Suspend main thread while calling background thread

Is it possible in Kotlin to call some code from the main thread that blocks until that code completes but at the same time does not block the actual UI? From my understanding, coroutines might be able to do that through a process that "suspends" executing code on the main thread until the other thread completes. But I have no idea how to do that if it is even possible.
Coroutines are pretty easy. Import them:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2'
Make your first async code:
private val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
//you need these because you need to handle them with Android Lifecycle.
fun fireACoroutine(){
corotutineScope.launch(Dispatchers.IO){
val someDataFromSuspendFunction = getStringAfterDelay()
withContext(Dispatchers.Main){
someTextView.text = someDataFromSuspendFunction
//switched to Main Thread.
}
}
var x = 0
x++ //you are outside of a coroutine
}
suspend fun getStringAfterDelay(): String = withContext(Dispatchers.IO){
delay(1000)
"Hello World"
}
Don't forget the to clean your hands when in Activity/Fragment:
override fun onDestroy() {
super.onDestroy()
completableJob.cancel()
}
Done! I also have an article about coroutines: Kotlin Coroutines, the non confusing one
I also suggest to know more about operators like launch, async or runBlocking.
Alternatively to coroutines, you may use RxJava to do what you request in questions. It does work in Kotlin like charm. Also, if you feel like you need to know more about both of them, stick to AsyncTask.

Coroutine not respecting mutex.lock hence more than one coroutine entering the critical-section at a time

I have a shared piece of code in a function, that should be accessed one at a time . I used mutex.lock/unlock to achieve this while working with coroutines.
public class TestAbc {
val mutex = Mutex()
suspend fun testfunction() {
mutex.lock()
arrayList.add("abc")
hashmap.put("abc", "efg")
mutex.unlock()
}
}
public class InitiatorClass{
val testAbc: TestAbc = TestAbc()
public fun startJob() {
GlobalScope.launch {
testAbc.testfunction()
}
}
}
I tested this by calling Start Job Function twice from a java class ,from different threads.
wanted only one coroutine to access critical-section at once, with help of mutex but its not working.I see multiple coroutines entering the lock.
My Bad, I was creating Multiple Instance of the Class InitiatorClass.
When i corrected it , everything works Fine .

Categories

Resources