Why Your TextView or Button Text Is Not Updating
Why does changing the text for a TextView
using the setText method not work sometimes? New Android developers sometimes fail to understand why changing the text does not appear to work. The text not updating also applies to other Views as well, such as the EditText
and Button
. Why this happens is explained in this article. This tutorial is also useful in helping to understand why the User Interface (UI) in your Android app might not be as responsive as expected. The underlying problem is the same. So if you are trying to find out why setText() on EditText, TextView, Button, etc. is not working as intended, or you UI is sluggish, then read on. The example source code includes a work around to help you out.
(To use the code in this tutorial it is assumed that Android Studio is installed, a basic app can be created and run, and the code in this article can be correctly copied into Android Studio. The example code can be changed to meet your own requirements. When entering code in Studio add import statements when prompted by pressing Alt-Enter.)
How to Change the Text on a TextView or EditText
When changing the TextView text the following code, or similar, becomes very familiar to all Android developers:
((TextView) findViewById(R.id.textView1)).setText("New Text");
For the above Java effectively creates an anonymous object and casts it as a TextView object. For those new to programming the above is equivalent to the following two lines but saves having to declare the TextView object.
TextView tv = (TextView) findViewById(R.id.textView1);
tv.setText("New Text");
App Code Runs on a Single Thread and Responds to Events
When setText("New Text")
is run the text is not updated immediately. Android is an event based system. Something happens on the device (the screen is touched, a key is pressed, a call comes in, etc.) and Android raises an event. An app is notified of an event and responds to it if required, often running the code that has been written. The app runs its code in a loop under the control of the Android Operating Systems (OS). This code loop is referred to as the app's thread of execution. There is only one thread and it is responsible for both running the app code and updating the display. The setText call posts a message to update the display, so the update does not happen immediately. Once remaining app code has run the UI messages are processed. It is then that the text changes. A running app's execution thread can be viewed as shown in this simplified diagram.
What this means is that changes to the UI can get delayed, and in some cases not appear to occur, when the running app code is doing a time intensive tasks. Intensive tasks include looping calculations, complex queries to large databases, accessing network resources, etc. A common scenario in not seeing the UI update is along these lines:
((TextView) findViewById(R.id.textView1)).setText("Starting Processing");
boolean processing = true;
int number_processed=0;
do {
/* Do some processing.
* processing set false
* when finished*/
++number_processed;
((TextView) findViewById(R.id.textView1)).setText( Integer.toString(number_processed)+ " Completed.");
} while(processing);
((TextView) findViewById(R.id.textView1)).setText( "Finished");
What happens is that the Finished message is seen but the progress messages are not.
Clogging Up the App Thread
It is easy to reproduce this scenario by creating a new simple app, dropping a Button on to the default activity_main.xml and then adding the above code to the button's onClick with a call to sleep()
in the loop. Here is an example layout with a Button and TextView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:onClick="ButtonClick"
android:text="Button" />
<TextView android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_marginLeft="14dp"
android:layout_toRightOf="@+id/button1"
android:text="Click to Start"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
And the code for the MainActivity.java file. The onClick attribute in the layout is used to wire up the event handler:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void ButtonClick(View arg0){
((TextView) findViewById(R.id.textView1)).setText("Starting Processing");
boolean processing = true;
int number_processed=0;
do {
/* Do some processing. */
try {
Thread.sleep(500);//Simulate intensive code
if(++number_processed>6)
processing=false;
} catch (InterruptedException e) {
e.printStackTrace();
processing=false;
}
/* processing set false
* when finished*/
((TextView) findViewById(R.id.textView1)).setText( Integer.toString(number_processed)+ " Completed.");
} while(processing);
((TextView) findViewById(R.id.textView1)).setText( "Finished");
}
}
If the UI is prevented from updating for more than about five seconds then what is known as an ANR (Application Not Responding) error occurs. The user may think the app has crashed completely and force close it, and then probably remove it from the device. Humans can detect systems responding down to tens of milliseconds, at about one tenth of a second pauses begin to degrade the experience of using a program. If no feedback is given for a few seconds users can become annoyed. It is easy to get the no response error with the above code, increase the number for the test in the if
statement from six to twelve, if(++number_processed>12)
, run the app and hit the Button several times.
A Helper Class Is Available to Run Another Thread
What is needed is a way to process the UI messages so that the display updates and still runs the intensive code. This is done by getting the app to define another execution thread on which the intensive code is executed. This stops the main thread from appearing to lock up when UI updates can be delay. This diagram show the idea:
Android has a built in class to make running intensive code easy, the AsyncTask
, it is covered in the Tek Eye tutorial article The Android AsyncTask Class Helps Avoid ANRs. Whenever a section of code is going to take some time to execute, maybe a second or more, then the use of AsyncTask is recommended. However, it can appear overkill when all you want to do is update a TextView to provide feedback before executing some code that may take a second or two.
Example Code That Has Variable Execution Time
The following example is going to be improved to provide user feedback without using AsyncTask. Good for simple feedback scenarios. Though AsyncTask is better for anything beyond a simple feedback message. The code in this example is going to check whether or not a long integer is a primary number or not. (The Java BigInteger
class is available for this but the routine here is used as an example.)
A prime number is any number that can be divided by itself and the number one, so the sequence begins 2, 3, 5, 7, 11, 13, 17, 19, 23, etc. and goes on for infinity. Routines to perform a primality test are well established and are usually based upon the Sieve of Eratosthenes.This function was derived from the script at Prime Curios! Primality Test web page.
public static boolean isPrime(long N) {
// Trial divide the positive integer N by the primes from 2
// Returns true if a prime divisor found, or false if none found
if (N%2 == 0) return false;//Eliminates evens
if (N%3 == 0) return false;//Eliminates multiples of three
// No need to go past the square root of our number (see Sieve of Eratosthenes)
long Stop = (long) Math.sqrt(N);
// Okay, lets "wheel factor" alternately adding 2 and 4
long di=2;
for(long i=5; i<=Stop; i+=di, di=6-di) {
if (N%i == 0) return false;
};
return true;
}
For most numbers the routine executes extremely quickly, however, a long integer can have up to 19 digits. If a 19 digit number is a prime then the inner loop can run several million times and a long pause in the program occurs. Here is the layout for the primality testing app.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<EditText android:id="@+id/editText1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:hint="Type a number"
android:textAppearance="?android:attr/textAppearanceMedium"
android:maxLength="19"
android:inputType="number"
android:selectAllOnFocus="true" />
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/editText1"
android:layout_below="@+id/editText1"
android:layout_marginTop="10dp"
android:onClick="CheckPrimeClick"
android:text="Is It a Prime?" />
<TextView android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/button2"
android:layout_below="@+id/button2"
android:layout_marginTop="10dp"
android:hint="Click 'Is It a Prime?'"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
And the code for the Is Prime app's MainActivity.java:
package com.example.isprime;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
long lngNum;//store the number to check
EditText et;//Allow user to type a long
TextView tv;//Show result of test
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// get a reference to the EditText and TextView
et = (EditText) findViewById(R.id.editText1);
tv = (TextView) findViewById(R.id.textView2);
}
public void CheckPrimeClick(View arg0) {
String number = et.getText().toString();
try {
lngNum=Long.parseLong(number);
}catch(Exception ex){
tv.setText("Error " + ex.getMessage() + " testing " + number);
return;
}
if(isPrime(lngNum)){
tv.setText(et.getText() + " IS a prime.");
}else{
tv.setText(et.getText() + " IS NOT a prime.");
}
}
/* Test for prime numbers
* http://primes.utm.edu/curios/includes/primetest.php
* also look at Java BigInteger isProbablePrime
*/
public static boolean isPrime(long N) {
// Trial divide the positive integer N by the primes from 2
// Returns true if a prime divisor found, or false if none found
if (N%2 == 0) return false;//Eliminates evens
if (N%3 == 0) return false;//Eliminates multiples of three
// No need to go past the square root of our number (see Sieve of Eratosthenes)
long Stop = (long) Math.sqrt(N);
// Okay, lets "wheel factor" alternately adding 2 and 4
long di=2;
for(long i=5; i<=Stop; i+=di, di=6-di) {
if (N%i == 0) return false;
};
return true;
}
}
When running the primality test app most numbers typed in fail the test immediately. Most numbers are not primes, and there is no problem with the response of the app. Likewise for small prime numbers, such as the eggshell number 77345993. Why eggshell? Well if that number is typed into an old desktop calculator with a Liquid Crystal Display (LCD) and the calculator is turned upside down, then it sort of reads EGGSHELL. Now try a really big prime number, a web search will reveal plenty, how about nineteen ones:
- 1111111111111111111 - Yes, strangely nineteen ones is a big number and a prime number as well.
Try it in the app and notice that it takes a few seconds for the routine to determine that it is a prime number. If tv.setText("Checking please wait.")
is added at the beginning of CheckPrimeClick
the same problem as the sleep example occurs. The UI update is blocked by the looping code.
Use A Timer and Handler To Provide Feedback
A useful solution to this problem without using the AsyncTask class is to introduce a very small delay between calling tv.setText and running the isPrime routine. During this delay the main thread continues and thus gets to up date the interface. The delay cannot be a Java sleep delay because that stops the program execution dead. Instead a Timer
is used. When the timer's TimerTask
is run after the small delay it sends an empty message that is caught by a Handler
and the Handler's Callback then executes the call to isPrime. A Timer is delared using Timer timer=new Timer()
. The Android in built Handler class is used (and the associated Handler.Callback
to save the need to implement one). The Callback is declared and code calling isPrime moved into it:
Handler.Callback callback = new Handler.Callback() {
public boolean handleMessage(Message msg) {
if(isPrime(lngNum)){
tv.setText(et.getText() + " IS a prime.");
}else{
tv.setText(et.getText() + " IS NOT a prime.");
}
return true;
}
};
The Handler can be newed the onCreate to use the Callback, handler = new Handler(callback)
. The TimerTask simply posts an empty message using the Handler.
class SmallDelay extends TimerTask {
public void run() {
handler.sendEmptyMessage(0);
}
}
The click handler schedules the delay with timer.schedule(new SmallDelay(), 100)
. The result is that the UI gets the chance to update whilst the code that was previously blocking the update still executes (after being kicked off by the timer). A straightforward solution for when a quick UI update is required when code can potentially hog the main thread for a short while.
Here is the complete code code for the MainActivity.java with the Timer implemented:
package com.example.isprime;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
long lngNum;//store the number to check
EditText et;//Allow user to type a long
TextView tv;//Show result of test
Timer timer=new Timer();//Used for a delay to provide user feedback
Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// get a reference to the EditText and TextView
et = (EditText) findViewById(R.id.editText1);
tv = (TextView) findViewById(R.id.textView2);
handler = new Handler(callback);
}
public void CheckPrimeClick(View arg0) {
String number = et.getText().toString();
tv.setText("Checking please wait.");
try {
lngNum=Long.parseLong(number);
}catch(Exception ex){
tv.setText("Error " + ex.getMessage() + " testing " + number);
return;
}
timer.schedule(new SmallDelay(), 100);
}
/* Test for prime numbers
* http://primes.utm.edu/curios/includes/primetest.php
* also look at Java BigInteger isProbablePrime
*/
public static boolean isPrime(long N) {
// Trial divide the positive integer N by the primes from 2
// Returns true if a prime divisor found, or false if none found
if (N%2 == 0) return false;//Eliminates evens
if (N%3 == 0) return false;//Eliminates multiples of three
// No need to go past the square root of our number (see Sieve of Eratosthenes)
long Stop = (long) Math.sqrt(N);
// Okay, lets "wheel factor" alternately adding 2 and 4
long di=2;
for(long i=5; i<=Stop; i+=di, di=6-di) {
if (N%i == 0) return false;
};
return true;
}
Handler.Callback callback = new Handler.Callback() {
public boolean handleMessage(Message msg) {
if(isPrime(lngNum)){
tv.setText(et.getText() + " IS a prime.");
}else{
tv.setText(et.getText() + " IS NOT a prime.");
}
return true;
}
};
class SmallDelay extends TimerTask {
public void run() {
handler.sendEmptyMessage(0);
}
}
}
Do not forget that for more complex UI feedback, including showing an active progress bar, then the AsyncTask solution is better.
See Also
- Download the final code for this example, available in is-prime-app.zip
- See the Android AsyncTask Class Helps Avoid ANRs article for an AsyncTask example.
- See the Android Example Projects page for more sample projects with source code.
- For a full list of all the articles in Tek Eye see the full site alphabetical Index.
Archived Comments
Fredo Velasco in January 2018 said: This whole thing was super confusing to me until I realized that the most important part of all this is:
The setText
call posts a message to update the display, so the update does not happen immediately. Once remaining app code has run the UI messages are processed. It is then that the text changes.
Which means that, if you call setText(), a message is posted to be fulfilled WHEN CODE AFTER THE CALL IS FINISHED. So if you have code that would take a while, right after your UI updating code, like finding a prime number, or setting other views etc, the setText() call will not be fulfilled until that other code is run.
So to have instantly updating UI, you should:
- Update your UI (by calling setText() etc) like you're already doing, and then
- Run the rest of the code after a small pause (with Handler.postDelayed() in Kotlin, or solution stated right here) or on a different thread with AsyncTask() or RxJava etc.
Don't use Thread.sleep() because that won't allow the UI thread to fulfill the UI updating code, it will just halt all execution; what you want to do is to post the message to update UI (with setText() etc.), and give the UI thread extra time to get the message, service the message request and update the UI before it has to start working on the code after the setText() call.
Dan at Tek Eye in January 2018 said: Yes, exactly. Perfect comment.
Author:Daniel S. Fowler Published: Updated: