Search code examples
javaandroidfor-loopsettext

Periodically updated text is not being displayed


I have a for loop in which I would like to call the setText method. This works without any errors except for the fact that the text does not change until the script is done running. What I want it to do is change the code every time I call the setText method. I attached my code below, it should be pretty strait forward. code:

package com.example.zoe.andone;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void startLoop(View view) {
        String string = getString(R.string.hello);
        ((TextView)findViewById(R.id.hello)).setText("starting...");
        for (int i = 0; i < 10; i++) {
            String updated = Integer.toString(i);
            ((TextView)findViewById(R.id.hello)).setText(updated);
            try {
                Thread.sleep(250);
            } catch(InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
        ((TextView)findViewById(R.id.hello)).setText("done!");
    }
}

Solution

  • That's what is called "dropping frames".

    It's time to learn!

    Before going to the answer, let's explain what's going on here.

    You probably already know that android has a frame-rate of 60fps (60 frames per sec), which means that every 1/60 sec the screen is rendered and displayed to the user (in order to provide the best definition). It does that by running some rendering code on your app (and everywhere else where there's something to be rendered) every 1/60 sec to compute how the displayed screen should look like at this particular ms (it's is called a frame).

    However, the code doing that is running on the UI thread, and if you're doing anything when the rendering tick kicks in, the android framework simply drops that frame (it won't compute it). In your case Thread.sleep(250); is what causes your frames to be dropped cause it's holding the UI thread for 250 ms in every iteration of your for loop (so it's 250 ms * (10 - 1), that's many frames).

    Dropping a frame means seeing no update on the UI.

    Solution

    Instead of using Thread.sleep(250); and dropping frames, you should schedule your update tasks to run every 250 ms. That's it.

    private int i = 0; // Does the trick!
    public void startLoop(View view) {
        String string = getString(R.string.hello);
        ((TextView)findViewById(R.id.hello)).setText("starting...");
            final Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (i < 10) {
                        String updated = Integer.toString(i);
                        i++;
                        updateHelloOnUiThread(updated);
                    } else {
                        updateHelloOnUiThread("done!");
                        handler.removeCallbacksAndMessages(null);
                        i = 0; // For future usages
                    }
                }
            },250);
    }
    
    private void updateHelloOnUiThread(final String text) {
         MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ((TextView)findViewById(R.id.hello)).setText(text);
                }
         });
    }
    

    Further reading

    Android UI : Fixing skipped frames

    High Performance Android Apps, Chapter 4. Screen and UI Performance