0

I am new to Android Development... Sorry for asking something so trivial. I don't know how to google this problem.

So let me explain:

I am doing the Android Development Course from Google and in the exercise you have a TextView that is repeatedly changed by an easy mathematical function 4 times after setContentViewis called. Between the changes Thread.sleep(1000) is called to delay the changes.

Expected behavior:

The Main Activity starts and you can see the TextView changing 4 times.

What actually happens:

The App start is delayed for as long as the calculations are set and then afterwards it will display the Main Activity with only the very last calculated result. In this case I would wait 4 seconds (with Thread.sleep(1000) being called 4 times) until the App is completely up and then you only see the result 60 because of 60 / 1.

This is the code:

private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        division()
    }

    private fun division() {
        val numerator = 60
        var denominator = 4
        repeat(4) {
            Thread.sleep(1000)
            Log.v(TAG, "${numerator / denominator}")
            val view: TextView = findViewById(R.id.division_textview)
            view.setText("${numerator / denominator}")
            denominator--
        }
    }

Thanks in advance. I hope someone knows why Google is suggesting this code, but it does not work on my machine.

Vince Ly
  • 1
  • 1
  • All you need is to check what's doing `Thread.sleep(1000)`. – Stanislav Bondar Jun 10 '22 at 14:03
  • Hey @StanislavBondar, thanks for replying so quickly. I have read into the several Thread types and I'm assuming the Main Thread is put to sleep in my case. However I would expect to see the changes between the sleeps... – Vince Ly Jun 10 '22 at 14:13
  • There is no delay between sleeps. It's a loop with sleep time 1000 * 4. Check `Handler` class and `postDelay` function this can help to solve your the problem – Stanislav Bondar Jun 10 '22 at 14:16
  • This is the third time I've seen someone run into this problem with this codelab - it's not you, they've written it (very) wrong. You should never block the main thread, which is exactly what their code does. I posted an answer here: https://stackoverflow.com/a/70646933/13598222 which shows you how you *can* use a thread to offload work, and there are other options like using coroutines, or what you've settled on (doing everything on the main thread, but posting runnables instead of grinding everything to a halt so you can wait) – cactustictacs Jun 10 '22 at 17:14
  • thank you @cactustictacs. I'm gonna check it out. I already started to think I was too stupid to finish their beginner's course. – Vince Ly Jun 10 '22 at 17:28
  • @VinceLy honestly the fact that you worked out it's only showing the final calculation is a sign you're cut out for this imo! The codelabs are mostly good from what I've seen, the fact that one doesn't even work at all (you're meant to be recording a video - of what??) tells me nobody actually tested it. And blocking the main thread *at all* is such a huge no-no in the Android world, never mind intentionally for seconds at a time - it's freezing the app, it can't respond, more than 4 seconds and you get an Application Not Responding popup and it's logged on Google Play as an issue. Weird stuff – cactustictacs Jun 10 '22 at 17:47
  • @cactustictacs thank you so much. Your comment encourages me a lot. And even though the exercise wasn't well made, I learned plenty of things while trying to solve the issue. – Vince Ly Jun 11 '22 at 11:20

4 Answers4

0

You need to make a delay by Timer instead of Thread.sleep()

Venus
  • 453
  • 2
  • 9
0

For training you can try something like this.

 private val timerHandler = Handler(Looper.getMainLooper())
 private val timerRunnable = Runnable {
    denominator--
    if(demominator != 0) {
       //UI changes here
       decrementTimer()
    }
 }

 private fun decrementTimer() {
    timerHandler.postDelayed(timerRunnable, DELAY_TIME)
 }

If you need to start first run immediately use timerRunnable.run() in other case call decrementTimer(). Also would be helpful to control handler with removeCallbacks function when activity goes to background

Stanislav Bondar
  • 6,056
  • 2
  • 34
  • 46
0

You can wait in another thread so you don't block the main thread, try this code it should work correctly, the code inside the lifecycleScope.launch will be moved to another thread so it will not block the UI:

private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    division()
}

private fun division() {
    val numerator = 60
    var denominator = 4
    lifecycleScope.launch {
        repeat(4) {
            delay(1000)
            Log.v(TAG, "${numerator / denominator}")
            val view: TextView = findViewById(R.id.division_textview)
            view.setText("${numerator / denominator}")
            denominator--
        }
    }
}

Note: You need to be sure that you add the lifecycle dependency in you app gradle

dependencies {
    ...
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
    ...
}
Mohamed Rejeb
  • 2,281
  • 1
  • 7
  • 16
0

Thanks for your answers everyone. Each of them helped me searching for the right topics. I liked the Handler Class the most and after some further searching, I came up with this solution:

private fun division() {
    val numerator = 60
    var denominator = 4
    val handler = Handler(Looper.getMainLooper())
    val divRunnable = object: Runnable {
        override fun run() {
            if (denominator != 0) {
                Log.v(TAG, "${numerator / denominator}")
                val view: TextView = findViewById(R.id.division_textview)
                view.text = "${numerator / denominator}"
                denominator--
                handler.postDelayed(this, 1000)
            }
        }
    }
    handler.post(divRunnable)
}

This is working exactly as I wanted it to work.

Vince Ly
  • 1
  • 1