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"
}
Related
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
I have a situation where I want to update an Activity's text fields as data comes in. The update only occurs when the simulation is completed, not while it is running (takes maybe 2 seconds to run).
Here is the code I have:
...
private var totalLoops = 0
private val updateDisplayTask = Runnable {
totalLoopsTV.text = totalLoops.toString()
totalEmailsSentTV.text = totalEmailsSent.toString()
totalPushesSentTV.text = totalPushesSent.toString()
private fun mainLoopFunction(currentTime: Long) {
...
totalLoops++
if(totalLoops % 20 == 0 || onDeckList.size == 0) {
Timber.w("UPDATING UI")
runOnUiThread(updateDisplayTask)
//handler.post(updateDisplayTask)
}
} //end of main loop
I've tried both runOnUiThread and handler/post as well as a few other things using Kotlin Coroutines, but nothing so far has worked. Can you see what I'm doing wrong here please? I see the logs of UPDATING UI so I know that the updates do get sent and I do see the last update (the only one I see) at the end.
Is this running on another thread, and then you run updateDisplayTask on the main thread? If you're updating totalLoops, totalEmailsSent and totalPushesSent on one thread (this worker thread) and reading them on another (main thread) then because of the way concurrency works, you might not actually see the new values on the main thread.
There are a few ways to manage synchronizing them, but if you're only writing the values on one thread (and you're not massively concerned about the possibility of some of the values changing partway through reading them, so they don't all match up) you can just use the #Volatile annotation on those variables to make them update across threads (works like the volatile keyword in Java).
If you care about atomic updates (everything changing together, can't read or write while something is in the middle of reading or writing them) you'll have to look into some kind of locking, like using synchronized methods and blocks. It's kind of a major (important!) subject, here's a post on it:
https://proandroiddev.com/synchronization-and-thread-safety-techniques-in-java-and-kotlin-f63506370e6d
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?
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.
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)