6

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?

TecBrat
  • 3,643
  • 3
  • 28
  • 45

3 Answers3

5

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.

cactustictacs
  • 17,935
  • 2
  • 14
  • 25
  • 1
    Most of the code labs have been pretty good, but you're right. This one is pretty screwy! Funny that it is the debugging one, and I think it mentions stack overflow in it. :-) – TecBrat Jan 10 '22 at 07:36
  • When I get back to my developing machine, I'll try your code and see if I can make it work. More importantly, I'll see if I fully understand it. Thank you. – TecBrat Jan 10 '22 at 07:47
  • 1
    @TecBrat Yeah a lot of it is about searching here for answers, which is fine, but feels like it should be its own section to me! If you're not familiar with threads, https://docs.oracle.com/javase/tutorial/essential/concurrency/ is worth a read - at least up to *Synchronization*, but that and *Liveness* are important too (this codelab has a liveness problem!). The Kotlin version is like a convenience builder, once you know the normal way it should make sense. Once you know the basics, this is the important Android stuff: https://developer.android.com/guide/components/processes-and-threads – cactustictacs Jan 10 '22 at 16:41
  • The code in this answer worked. Thanks. – TecBrat Jan 11 '22 at 01:07
2

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

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"
}
tadev
  • 204
  • 1
  • 5
  • Although Coroutines are the preferred method on Android these days, this is a bit out of scope for what the OP is trying to do (follow a codelab). You also did not provide a single link to what a coroutine is, does, or where the lifecycleScope is coming from. – Martin Marconcini Jan 10 '22 at 13:45