I'm very new to Android development. Can anyone help me with this snippet, I don't know why it works perfectly although I'm updating my TextView from the worker thread.
When I say works perfectly, I mean the TextView shows the value count without any problem.
So, My question is - "Is it really possible to update the UI from background thread and if not, where I'm wrong"
public class MainActivity extends AppCompatActivity {
TextView textView ;
private int count;
Button btn;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textview);
btn = findViewById(R.id.startbtn);
btn.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
listen();
}
});
}
public void listen(){
new Thread(new Runnable() {
#Override
public void run() {
long time = System.currentTimeMillis();
while(System.currentTimeMillis()<=time +10000) {
count++;
}
textView.setText(count+"");
}
}).start();
}
}
Use
runOnUiThread(new Runnable() {
#Override
public void run() {
// Add UI code here
}
});
All UI updates should be done on the main thread.
Related
Goal:
When you start the android app, the button should not be displayed after 5 seconds.
Problem:
The code doesn't work and what part am I missing?
Info:
*Im new in android
*The code is inspired from this page Android - Hide button during an onClick action
Thank you!
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button button2 = (Button) findViewById(R.id.btn_test);
button2.setVisibility(GONE);
new Thread(new Runnable() {
#Override
public void run() {
try
{
//dummy delay for 5 second
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
runOnUiThread(new Runnable() { //resetting the visibility of the button
#Override
public void run() {
//manipulating UI components from outside of the UI Thread require a call to runOnUiThread
button2.setVisibility(VISIBLE);
}
});
}
}).start();
}
}
This can be achieved in a simpler way. If your needed sequence is:
Start the app -> Display a button -> Wait 5 seconds -> Hide the button
final Button button2 = (Button) findViewById(R.id.btn_test);
button2.postDelayed(new Runnable() {
#Override
public void run() {
if (!isDestroyed() && !isFinishing()) {
button2.setVisibility(View.GONE);
}
}
},5000);
Otherwise, if you should display the button after 5 seconds after app launch, then just set button's visibility to GONE in your layout and change button2.setVisibility(View.GONE) to button2.setVisibility(View.VISIBLE) inside post delayed action
You need to set a listener to start your command, onCreate is the creation.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Button button2 = (Button) findViewById(R.id.btn_test);
button2.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
button2.setVisibility(GONE);
new Thread(new Runnable() {
#Override
public void run() {
try
{
//dummy delay for 5 second
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
runOnUiThread(new Runnable() { //resetting the visibility of the button
#Override
public void run() {
//manipulating UI components from outside of the UI Thread require a call to runOnUiThread
button2.setVisibility(VISIBLE);
}
});
}
}).start();
}
this code will hide the button AFTER onClick, start the thread, and after 5 seconds it will appear again.
How to do sample counter in Activity? This is not working.
public class MainActivity extends AppCompatActivity implements Runnable {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
run();
}
#Override
public void run() {
while (true) {
updateTv();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void updateTv() {
int counter = 100;
final TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(String.valueOf(counter));
counter--;
}
}
In onCreate() you're starting an infinite loop inside of the UI thread, blocking it completely. Alternatively you could use a Handler for periodic updates. Maybe using a bigger delay and stop it sometime.
public class MainActivity extends AppCompatActivity implements Runnable {
private final Handler mHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
run();
}
#Override
public void run() {
updateTv();
mHandler.postDelayed(this, 17);
}
public void updateTv() {
int counter = 100;
final TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(String.valueOf(counter));
counter--;
}
}
Anyway you should read What is the Android UiThread (UI thread) for sure.
Consider using Timer class which allows you to define a callback method that will be invoked at specified rate.
An example that fits your needs:
public class CounterActivity extends AppCompatActivity {
private TextView mCounterTextView;
private Timer mTimer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_counter);
mCounterTextView = (TextView) findViewById(R.id.counterTextView);
mTimer = new Timer();
mTimer.scheduleAtFixedRate(
new CounterTask(100), 0, TimeUnit.SECONDS.toMillis(1));
}
protected class CounterTask extends TimerTask {
protected int mCounter;
CounterTask(int initial) {
mCounter = initial;
}
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
mCounterTextView.setText(String.valueOf(mCounter));
}
});
--mCounter;
}
}
}
One more thing that should be noticed. As Timer executes it's own thread - it prevents you from updating your UI from outside of the main thread. In that case
you have to register a Runnable using runOnUiThread method.
Also, calling findViewById in a loop is not the best idea.
Android recommend update ui in the ui thread,but i found that i can update the ui in the non-ui thread directly like below:
public class MainActivity extends Activity {
private TextView textView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
new Thread(new Runnable() {
#Override
public void run() {
textView.setText("SecondThread");
}
}).start();
}
}
That's run correctly,but if i sleep the thread 1000ms:
public class MainActivity extends Activity {
private TextView textView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
new Thread(new Runnable() {
#Override
public void run() {
try {
**Thread.sleep(1000);**
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("SecondThread");
}
}).start();
}
}
I get the error"Only the original thread that created a view hierarchy can touch its views",i try to change the sleep value much times,i found when i set the value 135 or less,it can run correctly:
Thread.sleep(135);
Thread.sleep(134);
Thread.sleep(...);
That's very interesting!But why it happen?I can't find any way to make sense of that,is anyone can help me?thanks!
If you are trying to touch views from background thread you should consider using runOnUiThread method, which accepts runnable as argument in which you can update views
EDIT: Also I would recommed you to use AsyncTask to achieve your goals, it has two callbacks onPreExecute and onPostExecute, whiche are invoked on the UI thread
so will always work
new Thread(new Runnable() {
#Override
public void run() {
try {
**Thread.sleep(1000);**
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runable(){
#Override
public void run(){
textView.setText("SecondThread");
});
}
}).start();
There is another way to use the Handler()
My goal is when the user tap start button, letters "o" "n" "o" "m" and so forth will appear at the center of the screen. "o" will appear first then after a few seconds will be replaced by "n" then "o" and so forth.
note: for brevity, i just make the guessword = onomatopoeia, first. In reality, guessword will changes every time i tap the start bottom.
this is the code:
private String guessword = "onomatopoeia";
private TextView showchar;
private int n = guessword.length();
private char letArray[]= guessword.toCharArray();;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
addStartListener();
}
public void addStartListener(){
Button start = (Button) findViewById(R.id.start);
showchar = (TextView) findViewById (R.id.charView);
start.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Thread thread = new Thread()
{
#Override
public void run() {
try {
for(int i = 0 ; i < n ; i++) {
sleep(1000);
showchar.setText(letArray[i]);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
}
});
}
thanks for the help
I decided to implement runonuithread but still it crashes:
this is the updated version:
private String guessword = "onomatopoeia";
private TextView showchar;
private int n = guessword.length();
private char letArray[]= guessword.toCharArray();
private Handler handler;
private int i = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
handler = new Handler();
showchar = (TextView) findViewById (R.id.charView);
}
public void startGame(View view){
new Thread() {
public void run() {
while(i++ < n) {
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
showchar.setText(letArray[i]);
}
});
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
use this code for setting the text in your textview
runOnUiThread(new Runnable() {
#Override
public void run() {
showchar.setText(letArray[i]);
}
});
You are updating ui from a thread which is not possible.
showchar.setText(letArray[i]);
UI must be updated ui thread.
All you are doing is repeatedly setting value to TextView you can use Handler with a delay for this purpose.
You could use runOnUiThread also but i don't see the need for a thread for what you are doing.
Use a Handler. You can find an example #
Android Thread for a timer
I have three layouts:
Layout1
-->onClick()-->show
Layout2
-->wait three seconds-->show
Layout3
The problem is that Layout2 is not shown. To set the layouts I use
setContentView(int);
The relevant code might be:
public class TrainingActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout1);
final Button inputButton = (Button)findViewById(R.id.inputButton);
inputButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
changeLayouts();
}
});
}
public void changeLayouts() {
setContentView(R.layout.layout2);
try {
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setContentView(R.layout.layout3);
}
}
My idea was that Android might use something like an "Event-Loop" (like Qt) and my method would block the control to get back to the "Event-Loop" which would make the layout displayed.
But I couldn't find my error.
The problem why your layout2 is not shown is because of TimeUnit.MILLISECONDS.sleep(3000); - what you are doing here is you put your UI thread into sleep, so UI thread cannot process your request to change layout. And when it wakes up - it immediately sets layout3 that's why layout2 is not shown.
You might consider using Handler.postDelayed(Runnable, long) to postpone execution
So this should work as you expected:
public class TrainingActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout1);
final Button inputButton = (Button)findViewById(R.id.inputButton);
inputButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
changeLayouts();
}
});
}
public void changeLayouts() {
setContentView(R.layout.layout2);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
setContentView(R.layout.layout3);
}
}, 3000);
}
}
Try this, it will surely work
public void changeLayouts() {
setContentView(R.layout.layout2);
Thread Timer = new Thread(){
public void run(){
try{
sleep(3000);
} catch(InterruptedException e){
e.printStackTrace();
} finally {
setContentView(R.layout.layout3);
}
}
}; Timer.start();
}