How to create timer in Android Studio ( kotlin )? - android

i wanted to create a program in Android Studio, using kotlin, where i use timer which does an action every ~20 milliseconds and runs infinitely. I used something like this:
object : CountDownTimer(10000,20){
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
start()
}
}.start()
But the problem is, after some time, the timer starts slowing down, and when 1 sec should pass, it actually is 6 seconds. Changing the actions to 40 milliseconds or more only helps for a little while, because later it still slows down. I saw that people used timer that actually works like this without any problem, but it was in Java, and when i tried to change it into Kotlin with Android Studio's help (i never used Java), it ended up not working (after launching, the app crashed everytime). Does someone know how to create or use different timer other than CountDownTimer that i showed above which is in kotlin?
Edit:
I forgot to add the code that works for me. It's basically the one i accepted as answer but i had to make some changes to make it work, so here it is:
val timerName = lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
lifecycleScope.launch {
doSomething()
}
delay(20L)
}
}

It most probably happens because the function in onTick() and onFinish() are invoked within the same coroutine that the counter works, so probably you would need to launch another coroutine there to not block the timer scope
Also this CountDownTimer API is experimental at this moment so I guess it is not really recomended to use it, probably you could do something like this:
val timerJob = scope.launch(Dispatcher.IO) {
while (isActive) {
scope.launch { doSomething() }
delay(20L)
}
}
I didn't test it but it should have exactly same behavior (but not blocking the timer), it will run until the scope will cancel itself (f.e. lifecycle scopes) or you will cancel it manually by calling timerJob.cancel()
The isActive boolean takes care of checking if coroutine is still active (so the loop won't "leak")
Unfortunately if you run some heavy stuff in this doSomething() call that will exceed this 20 milisec delay there will happen some concurrency issues so it should be simple
If your "game" is so heavy that this 20 milisec delay will be too small then most probably using coroutines is not the best approach for your idea

Related

Avoid UI Blocking while running an operation that requires to be run on the main thread

I use an API that has a method, let's say run() that must be executed on the main thread, or else it'll throw an exception. Now, I've tried coroutines, and it doesn't work in a standard launch{...} block, which is understandable. Now, since it is a little long running task, I wish to show a UI to the user, indicating the same, i.e., a process is taking place. Now, I do not require assistance on the animation logic, but I cannot understand how is the animation supposed to keep up alongside all the heavy IO stuff that may be going on on the main thread.
Also, I've been experiencing some very odd behaviour in this Composable. Kingly have a look,
#Composable
fun CustomC() {
var trigger by remember {
mutableStateOf(false)
}
val color by animateColorAsState(targetValue = if (trigger) Color.Gray else Color.Cyan)
Surface(Modifier.fillMaxSize()) {
Text(
modifier = Modifier.background(color),
text = "Running"
)
}
// I tried this but this seems to produce a crash, indicating that the run method is not running on the main thread, but how? Removing this LaunchedEffect removes the error.
// LaunchedEffect(Unit){
// delay(2000)
// trigger = true
// }
run() // Must be executed on the Main Thread
}
This app crashes if I put that LaunchedEffect block over there, but it is not even interacting with run() in any way, per my knowledge. And another strange behaviour is as follows:
#Composable
fun CustomC() {
var trigger by remember {
mutableStateOf(false)
}
val color by animateColorAsState(targetValue = if (trigger) Color.Gray else Color.Cyan)
Surface(Modifier.fillMaxSize()) {
Text(
modifier = Modifier.background(color),
text = "Running"
)
}
trigger = true
run() // Must be executed on the Main Thread
}
Now, you would expect the Composable to be turning Cyan before the run method is called, right? IT DOESN'T!! IT JUST DOESN'TTTT
It just starts executing run() and then finally AFTER the method is done executing, the Composable turns cyan. This clearly implies that the recompositions are blocked while the method is running, so all I need is a way to get around that.
EDIT: An important piece of information that I missed earlier, when I call the run() method inside of a LaunchedEffect, the method seems to work fine, i.e., the app doesn't crash, but the UI is still blocked.
Also, if I call the method inside a launch block WITHIN a LaunchedEffect, the same thing happens as above, where the method runs fine but the UI is clogged. What is the role of launch here then?
FINAL EDIT: A very rare thing I saw in this scenario was when the crash appeared, it did not throw any sort of an exception. It just raised an error like so:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x2f6000412f6018 in tid 29693
I got this error from my search history, and it pointed me to some things related to programming in the NDK, which I do not. Also, now no matter what I do, the error won't come up. Seems like a glitch in Android or Studio.
Since the cause of the crash seems to be the method was not given access to I/O by the system, the problem was resolved using a coroutine with I/O access.
Now, in my ViewModel, I wrapped the run method in a coroutine like this
suspend fun runWithIO(){ // custom method
withContext(Dispatchers.IO){
run() // The original method, provided by the API
}
}
This satisfies all the constraints, whatsoever - Runs on a thread with I/O access, does not block the Main Thread, uses best practices like coroutines without any side-effects.
So, the explanation was posted by Johann in the first comment on the original post which has just been adapted to my specific use-case here.
So, if an API that you might be using states that the methods are supposed to be run only on the main thread, you should probably play around for a while with different coroutine contexts, since the API may only require the main thread to perform I/O operations, which can also be performed in a simple coroutine like this. If the method does indeed require the main thread, there's also Dispatchers.Main to assist you with that, you can run only the required part of a function on the Main Thread, but please note, this call WILL BE blocking.

How to use Thread.sleep() in Kotlin

This comes from near the end of the codelab found here:
Intro to debugging - Debugging example: accessing a value that doesn't exist
This is all inside the MainActivity.kt file
Here's my onCreate
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val helloTextView: TextView = findViewById(R.id.division_textview)
helloTextView.text = "Hello, debugging!"
division()
}
//...
Here's the division function provided by Google, but with 2 changes I'll explain in a moment...
fun division() {
val numerator = 60
var denominator = 4
repeat(4) {
Thread.sleep(3000)
findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}")
Log.v(TAG, "${numerator / denominator}")
denominator--
}
}
The instructions make it seem like they expect sleep() to accept seconds, but AS indicates it expects millis, so I changed their 3 to 3000. I added Log.v(TAG, "${numerator / denominator}") to see what was happening because it wasn't doing as expected.
The point of this was to have the emulator create a gif of the quotients being updated. This is supposed to be helpful when debugging.
Nothing displays on screen, not even the app's name, until the repeat() finishes.
The logs happen in 3 second intervals, as expected.
Why is the layout waiting, and how do I make it update on each iteration of the loop?
I honestly have no idea what that Codelab is doing, based off the code they provide. The app isn't going to render anything (not the layout, not any changes to the layout) before onCreate finishes, and onCreate won't finish until it's run all its code, including that repeat block in the division function it calls.
division isn't starting any worker threads, so all Thread.sleep is doing is blocking the main thread - it's hanging the app. And you're right, sleep does take a millis value, not seconds - I get the feeling they didn't actually run this code, it's full of other mistakes and inconsistencies that honestly made it hard to work out what you were meant to be doing. Change which Log.d call? The ones in onCreate? (They actually mean the Log.v call in division, I assume)
Here's how you'd use a thread in Kotlin - you need to create a new one (so you're off the main thread, so it can actually finish creating the activity and run the UI):
fun division() {
// create a new thread (and start it immediately)
thread(start=true) {
repeat(4) { i ->
Thread.sleep(3000L)
// assuming you've done the ``findViewById`` and assigned it to a variable
runOnUiThread { divisionTextView.text = "$i" }
}
}
}
That's just updating with the current repeat number (i) for brevity, but the important things are:
you're creating a new thread to do the work (and sleeping)
you're using runOnUiThread (which is a method on the Activity) to do the text updating on the main/UI thread (same thing). If you try to touch the UI from another thread it will crash
Another way you can do that is to post a runnable to the UI thread through a view, may as well use that TextView:
divisionTextView.post { divisionTextView.text = "$i" }
Coroutines would be a better idea here (you can run them on the main thread without it blocking, so you don't have to worry about switching threads to update the UI, or any thread safety stuff) but that's the basics of doing a thread. Genuinely have no idea what's going on in that codelab.
Nothing displays on screen, not even the app's name, until the repeat() finishes. The logs happen in 3 second intervals, as expected. Why is the layout waiting?
It is due to Activity life cycle
When the activity is in the Created state, you can't see the UI.
When the activity is in the Started state, UI becomes visible but you can't interact with it.
When the activity is in the Resumed state, UI is visible and you are able to interact with it.
Since you're calling the division() function in the onCreate() callback, you won't see the activity UI.
how do I make it update on each iteration of the loop?
One way of doing that is to run your division() function in a new thread. By doing so, the main tread displays the UI (you'll be able t see it). Then use runOnUiThread to update your division_textview text.
fun division() {
val numerator = 60
var denominator = 4
thread(start=true) {
/* The loop only repeat 4 times to avoid a crash*/
repeat(4) {
Log.v(TAG, "$denominator")
// Set division_textview text to the quotient.
runOnUiThread { findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}") }
// Wait for 1 second
Thread.sleep(1000L)
denominator--
}
}
}
You can use Corountines. delay function is very easy to use
lifecycleScope.launch {
myTextView.text = "Starting"
delay(1000L)
myTextView.text = "Processing"
delay(2000L)
myTextView.text = "Done"
}

Android coroutine job join confusion

so I've been looking at the coroutines for past few days and found quite interesting piece of code
val input = MutableStateFlow(5)
lifecycleScope.launch {
input.collectLatest {
val job = launch {
doSomething1()
delay(1000L)
doSomething2()
}
job.join()
}
}
I've been debugging it and im not sure why do we need that job.join in there, how I understand the whole flow right now:
Lets say we push fast 3 values to input, collectLatest will terminate previous unfinished box, but since we are launching new coroutine its job is not terminated, so after pushing fast 3 values we will always reach doSomething2 for each value, if we would skip launch inside collectLatest we would reach only one doSomething2 - but now coming to the job.join - it should suspend coroutine until the job is done, but since we are not doing anything else in this coroutine is it really needed? Does it give any value? From my tests it doesn't really matter if we have that job.join or not, what I am missing in here?

Is it okay, resource wise, to have an infinite loop refresh a TextView (every n seconds) with a string from context.getString()?

So, I have this function:
inline fun runInLoop(intervalInMillis : Long = 1_000, crossinline function : suspend () -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
while (true) {
function.invoke()
delay(intervalInMillis)
}
}
}
And I use it like so:
Time.runInLoop(10_000L) {
binding.timeCreatedTextView.text = mContext.getString(R.string.time_created,
Time.unixToRelativeTime(item.timeCreated))
}
Basically, I use this function to loop every one second to refresh "timeCreatedTextView"s with DateUtils.getRelativeTimeSpanString(), and that is used the most in RecyclerView rows, so multiple infinite loops will be running on a coroutine in the background.
My question is, is anything that I'm doing potential source for memory leaks or high memory usages, since I'm using DateUtils to get the relative time, or doing string formatting every one second for however long the view is on screan?
You are creating a custom CoroutineScope. That is fine, but then it is your job to indicate when that scope is no longer needed, so it stops running your infinite-loop coroutines. Right now, you do not appear to be doing that.
A better solution is to use a CoroutineScope tied to the lifetime of your UI. Since you are using a DialogFragment, the viewLifecycleScope extension property on Fragment would be a likely choice. Then, your coroutines will be cleaned up when the fragment is destroyed. You might use that scope directly, or you might still create your own custom scope, but with viewLifecycleScope as a parent, so you can control individual timers (cancelling them if they are no longer needed) while still getting lifecycle awareness.

How Kotlin coroutines are scheduled

I've been reading a lot of articles and watching a lot of videos on Kotlin co-routines lately and, despite my efforts, I still can't make sense of them in my head.
I think I've finally found a way to illustrate my problem:
class MyViewModel() : CoroutineScope {
override val coroutineContext = Dispatchers.Main + Job()
fun foo() = launch(handler) {
Log.e("test", "A")
}
}
class MainActivity : Activity() {
override fun onCreate() {
MainViewModel().foo()
Log.e("test", "B")
}
}
The output of this is:
E/test: B
E/test: A
And I don't get how this can be the case, I'm using only one thread (the main thread). If my code executes sequentially, by the time I reach the line log(B)... log(A) should have already be printed.
Does the coroutines library use other threads internally to accomplish this? This is the only explanation I could come up with but haven't found anything saying so in the docs.
PS: Sorry for throwing android into the mix but this code:
fun main() {
GlobalScope.launch(Dispatchers.Unconfined) { // launch new coroutine in background and continue
print(Thread.currentThread().name + "World!") // print after delay
}
(0 .. 1000).forEach { print(".") }
}
seems to work as expected and prints:
main #coroutine#1World!...........................
because 1 thread == sequential work
Hope my question makes sense, thanks for reading!
Under the hood, the Main dispatcher uses a Handler to post a Runnable to the MessageQueue. Basically, it’ll get added to the end of the event queue. This means it will execute soon, but not immediately. Hence why “B” gets printed before “A”.
You can find more information in this article.
EDIT by OP (read the article above before reading this):
Just wanted to clarify why the android example above was working fine, in case someone is still wondering.
fun main() {
GlobalScope.launch(Dispatchers.Unconfined) { // launch new coroutine in background and continue
print(Thread.currentThread().name + "World!") // print after delay
}
(0 .. 1000).forEach { print(".") }
}
We're setting GlobalScope to use the UNCONFINED dispatcher, and this dispatcher has isDispatchNeeded set to false.
false means "schedule in the current thread", and that's why we see the logs printing sequentially. UNCONFINED should not be used in regular code.
All other dispatchers have isDispatchNeeded set to true even the UI dispatcher. see: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/is-dispatch-needed.html
(btw GlobalScope uses the Default dispatcher if we don't specify one)

Categories

Resources