Home > Software design >  Only the original thread that created a view hierarchy can touch its views. runOnUIThread and Handle
Only the original thread that created a view hierarchy can touch its views. runOnUIThread and Handle

Time:12-12

I'm trying to start a countup timer once I start a new activity on a button click from the first activity, however I keep getting this error. Adding a delay doesn't seem to fix the issue as well and I don't want the delay to be there anyways as there will be an awkward pause.

public class CallActive extends AppCompatActivity {

    TextView timerText;

    Timer timer;
    TimerTask timerTask;
    Double time =0.0;

    @Override
    protected void  onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getSupportActionBar().hide();
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_callactive);

        Intent intent = getIntent();
        String text = intent.getStringExtra(MainActivity.EXTRA_INFO);
        TextView txtCaller = (TextView) findViewById(R.id.txtCaller2);
        txtCaller.setText(text);

        timerText = (TextView) findViewById(R.id.timerText);
        timer = new Timer();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                startTimer();
            }
        });
    }
        private void startTimer() {
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    time  ;
                    timerText.setText(getTimerText());
                }
            };
            timer.scheduleAtFixedRate(timerTask, 0, 1000);
        }
        private String getTimerText() {
            int rounded = (int) Math.round(time);

            int seconds = ((rounded % 86400) % 3600) % 60;
            int minutes = ((rounded % 86400) % 3600) / 60;

            return formatTime(seconds,minutes);
        }
        private String formatTime(int seconds, int minutes)
        {
            return String.format("d",minutes)   " : "   String.format("d",seconds);
        }
    }

I tried using runOnUIThread and Handlers but the same error persists

CodePudding user response:

run() of your TimerTask will be called on a background thread. So, your runOnUiThread() needs to go inside of that run():

        private void startTimer() {
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            time  ;
                            timerText.setText(getTimerText());
                        }
                    });
                }
            };

It would be simpler and more efficient to eliminate TimerTask and Timer and use postDelayed().

CodePudding user response:

You look like a little confused about UI elements and UI thread. All UI elements are created by the UI (or main) thread and they can only be modified by it. So although you start you timer using runOnUiThread, this doesn't mean that the TimerTask will run in the main thread. This is where you get confused.

// You define the timertask here but it will not run in this context which is the main thread context
timerTask = new TimerTask() {
    @Override
    public void run() {
        time  ;
        // This textview is still will be touched from another thread that is not main thread
        timerText.setText(getTimerText()); 
    }
};

You must still execute the code that modifies the UI object only within the main thread like following:

timerTask = new TimerTask() {
    @Override
    public void run() {
        time  ;
        runOnUiThread(()-> timerText.setText(getTimerText()));
    }
};
  • Related