In the code section given below, I have run a thread on a button click. At first, the thread will set text to the textView, then it will sleep for 6 seconds. But, in reality on the button click, at first, the thread is sleeping for 6 seconds and then it is setting text to the textView. Now, why is this mismatch of statement execution flow happening?
public class MainActivity extends AppCompatActivity {
EditText editText;
Button button;
TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.editText);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
button.setEnabled(false);
runthread();
}
});
}
private void runthread() {
final String s1 = editText.getText().toString();
Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(s1);
button.setEnabled(true);
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
});
}
}
Android main thread is built upon two core concepts Looper and Handler. It translates to a message loop related to the thread, and messages interactor. All the ui staff benefit on that. For instance used by you setText is nothing more then sending a new message to the looper, after some time the message is resolved by some handler. As you may guess firstly each message is enqueued.
If you look at your example through the prism of above, you see steps listed below.(All steps are taken on main thread / main looper)
Handler handler = new Handler(); <-- this handler is related to main looper
handler.post(new Runnable() { <-- enqueue new message to main looper
#Override
public void run() {
runOnUiThread(new Runnable() { <-- enqueue new message
#Override
public void run() {
textView.setText(s1); <-- enqueue new message of setting text
button.setEnabled(true); <-- enqueue new message of enabling button
try {
Thread.sleep(6000); <-- suspend thread for 6 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
});
According to above, you suspend thread before all the messages including setting text and enabling button are handled. The consequence of that is reversed order.
#UPDATE Then, how can I enqueue this sleep() method also?
val handler = Handler();
handler.post {
runOnUiThread {
textView.text = "ABC";
button.isEnabled = true;
}
}
handler.postDelayed({
try {
Thread.sleep(6000);
} catch (e: InterruptedException) {
e.printStackTrace();
}
}, 16) <-- 16 milliseconds is a time between frames
Related
I want a delay for two seconds. and every 2 seconds I want to change the text, and for that, I am using handler like this, but it's not working it's only showing hello. it's not changing at all it only shows what I write second. The code is like this,
private Handler handler = new Handler();
int i=5;
private TextView textView ;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.hello);
textView.setText("Android Things !!");
hello_first.run();
}
private Runnable hello_first = new Runnable() {
#Override
public void run() {
textView.setText("Nice Work");
handler.postDelayed(this,5000);
textView.setText("Hello");
handler.postDelayed(this,2000);
i = i+1;
if(i==5)
handler.removeCallbacks(this);
}
};
You are using postDelayed incorrectly. It looks like you expect it to work the same way Thread.sleep would work. However that is not the case.
Here is a correct implementation of what you are trying to achieve:
private Handler handler = new Handler();
private TextView textView;
private int i = 0;
private boolean flip;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.hello);
handler.post(hello_first);
}
private Runnable hello_first = new Runnable() {
#Override
public void run() {
if(++i == 5) {
handler.removeCallbacks(this);
return;
}
if(flip) {
textView.setText("Nice Work");
} else {
textView.setText("Hello");
}
flip = !flip;
handler.postDelayed(this, TimeUnit.SECONDS.toMillis(2));
}
};
I Hope this will work for you.
Handler handler = new Handler();
Runnable task = new Runnable() {
#Override
public void run() {
textView.setText("Nice Work");
handler.postDelayed(this,2000);
textView.setText("Hello");
handler.postDelayed(this,2000);
}
};
task.run();
For Stopping task
handler.removeCallbacks(task);
Its easy to use like
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
textView.setText("Hello");
},2000);
handler.postDelayed(new Runnable() {
#Override
public void run() {
textView.setText("Nice Work");},5000);
A Handler allows you to send and process Message and Runnable objects
associated with a thread's MessageQueue.
Rectify your postDelayed method.
Causes the Runnable r to be added to the message queue, to be run
after the specified amount of time elapses.
DEMO STRUCTURE
textView.setText("Nice Work");
final Handler handlerOBJ = new Handler();
handlerOBJ.postDelayed(new Runnable() {
#Override
public void run() {
// YOUR WORK
textView.setText("Hello");
}
}, 5000); // 5S delay
You can Log and see what happened actually...
Every time you call handler.postDelayed(this, 5000);
it will create two Runnable instance and send them to the handler. So the number of runnable in the queue increase very quickly.
You can set a text list and index, and then throw the runnable to the handler and postDealyed as 2000 milliseconds. Use the text list and index to see what text should be set to the textview.
try this code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.hello);
textView.setText("Android Things !!");
handler.postDelayed(hello_first,5000);
handler.postDelayed(hello_second,2000);
}
private Runnable hello_first = new Runnable() {
#Override
public void run() {
textView.setText("Nice Work");
}
};
private Runnable hello_second = new Runnable() {
#Override
public void run() {
textView.setText("Hello");
}
};
After click button I would like to change its color, then wait one second and change its color back.
This is my code:
public void click(final View view) throws InterruptedException {
final Button btn = findViewById(view.getId());
btn.setBackgroundColor(Color.parseColor("#0000ff"));
btn.setClickable(false);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
t.join();
btn.setBackgroundColor(Color.parseColor("#e2e2e2"));
btn.setClickable(true);
}
It doesn't work. I've checked it with more complex code and debugger and it looks like all UI changes are made collectively after finish this function.
I've found this thread: apply ui changes immediately and tried to put setBackgroundColor() and setClickable() into runOnUiThread function:
runOnUiThread(new Runnable() {
#Override
public void run() {
btn.setBackgroundColor(Color.parseColor("#0000ff"));
btn.setClickable(false);
}
});
But it also doesn't work. What should I do?
Something like this :
private final Handler handler = new Handler();
public void click(final View view) {
view.setBackgroundColor(Color.parseColor("#0000ff"));
view.setClickable(false);
handler.postDelayed(() -> {
view.setBackgroundColor(Color.parseColor("#e2e2e2"));
view.setClickable(true);
}, 1000);
}
#Override protected void onPause() {
super.onPause();
handler.removeCallbacks(null);
}
The question is not very clear. However, I am trying to summarize the question that I have understood from your question.
You are trying to set a button's background color on clicking on it and change it back after some time. If this is the situation, then I think your idea of how threads work is wanting.
In your code, the button will change the color immediately as the sleep that you are using is running in another thread (other than UI thread). The code is executed correctly, however, you cannot see the effect of the Thread.sleep as its running in a separate thread.
So all you need to do here is to change the background color again inside the thread. Modify your code like the following.
public void click(final View view) throws InterruptedException {
final Button btn = findViewById(view.getId());
btn.setBackgroundColor(Color.parseColor("#0000ff"));
btn.setClickable(false);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(1000);
btn.setBackgroundColor(Color.parseColor("#e2e2e2"));
btn.setClickable(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
This should work.
I have created a demo trying to show what the code will do.
However, using Handler in case of updating UI elements in this specific case is recommended. Please see the comments below.
public void click(final View view) throws InterruptedException {
final Button btn = findViewById(view.getId());
btn.setBackgroundColor(Color.parseColor("#0000ff"));
btn.setClickable(false);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
btn.setBackgroundColor(Color.parseColor("#e2e2e2"));
btn.setClickable(true);
}
}, 1000);
}
Not sure why that wouldn't work, but I've done something similar with
delayHandler = new Handler();
delayHandler.postDelayed(new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
//change stuff on ui
}
});
}
}, 1000);
if that doesn't work the only other functional difference in my code is that instead of btn being a final Button it's a private global variable in my activity.
Hope the following code will help :
btn.setBackgroundColor(Color.RED); // color you want for a second
new CountDownTimer(1000, 1000) {
#Override
public void onTick(long arg0) {
// TODO Auto-generated method stub
}
#Override
public void onFinish() {
btn.setBackgroundColor(Color.BLUE); //to change back color to prior state
}
}.start();
Try this,i think it's work for you..
final Button bPdf = findViewById(R.id.pdf);
bPdf.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
bPdf.setBackgroundColor(Color.parseColor("#0000ff"));
new CountDownTimer(1000, 50) {
#Override
public void onTick(long arg0) {
// TODO Auto-generated method stub
}
#Override
public void onFinish() {
bPdf.setBackgroundColor(Color.parseColor("#e2e2e2"));
}
}.start();
}
});
I am trying to have a thread that waits for user input for 3 seconds, if the user clicks the button execute "done" and if he doesn`t click in 3 seconds execute "not". I searched through stackoverflow but couldn't find an exact and simplified answer. Sorry if this is a repeat and/or noob question.
Here is the code I have for the moment but feel free to offer me another way, couldn't solve it with this;
private Thread thread;
TextView maintext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MainActivity myActivity = this;
maintext = (TextView) findViewById(R.id.mainText);
}
public void start(View view){
changeText("Waiting.");
thread= new Thread(){
#Override
public void run(){
try {
synchronized(this){
wait(3000);
}
}
catch(InterruptedException ex){
}
}
};
thread.start();
}
public void done(){
maintext.setText("You pressed the button in 3 seconds");
}
public void not(){
maintext.setText("You failed");
}
public void changeText(String text){
maintext.setText(text);
}
public void click(View view){
synchronized(thread){
thread.notifyAll();
}
}
How about a CountDownLatch?
Construction (in your class instance variable declaration, i.e. the place where thread is declared now):
CountDownLatch latch = new CountDownLatch(1);
In your waiting thread (called start), instead of creating and starting that Thread:
// wait for it to be notched or for timeout
boolean buttonPressed = latch.await(3, TimeUnit.SECONDS);
if (buttonPressed) {
....
}
And in your button click listener (that is not shown in your post, usually it's configured like clickButton.setOnClickListener( new OnClickListener() {):
latch.countDown();
Later addition
But please note that if that start() method is run in a UI thread, waiting on it will simply freeze the UI which is not what you want. Another approach would be to use something like events. Define two events: 'three seconds passed' and 'the button was pressed' and process them.
Instance variables:
private boolean buttonPressed = false;
private boolean expired = false;
private final Object monitor = new Object();
Timer initialization:
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
synchronized (monitor) {
if (!buttonPressed) {
// change text to 'You failed'
expired = true;
timer.cancel();
}
}
}
}, 3000);
In your button click listener:
synchronized (monitor) {
if (!expired) {
// change text to 'You pressed the button in 3 seconds'
buttonPressed = true;
timer.cancel();
}
}
There is no need to block any thread.
May be this answer will help you
private boolean isActive; //globally declare
changeText("Waiting.");
Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (!isActive) {
isActive = true;
}
}
};
handler.sendEmptyMessageDelayed(0,3000);
public void onClick(View v) {
if (!isActive) {
done();
} else {
not();
}
}
public void done() {
mainText.setText("You pressed the button in 3 seconds");
}
public void not() {
mainText.setText("You failed");
}
public void changeText(String text) {
mainText.setText(text);
}
I hope this will resolve your issue.
I am working on an Android application in which I allow the users to enter text as many time they want in a single minute. But the thread is not terminating after a minute. My code:
public void startTimer(View view) throws Exception {
final Thread t = new Thread(new Runnable() {
final Button myButton = (Button) findViewById(R.id.myButton);
EditText mEdit = (EditText) findViewById(R.id.myTextInput);
#Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
myButton.setOnClickListener(
new Button.OnClickListener() {
public void onClick(View v) {
Log.d("Tag", mEdit.getText().toString());
}
}
);
}
}
});
t.start();
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.schedule(new Runnable() {
#Override
public void run() {
t.interrupt();
}
},1, TimeUnit.MINUTES);
}
better use CountDownTimer http://developer.android.com/reference/android/os/CountDownTimer.html
In your solution you try do something with UI state in not main thread - this can be a issue.
Your thread isn't "taking input". It's continuously making and installing new onClick listeners.
You don't need a thread for what you're trying to do. You need a timer that goes off after one minute, and you need a listener that either accepts the input or rejects it depending on whether the timer has gone off yet or not.
You have two solutions you can do:
1st Solution:
You can simply use a handler.
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
t.interrupt();
}
}, 60000);
2nd Solution:
You can also use a CountDownTimer.
new CountDownTimer(60000, 1000) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
t.interrupt();
}
}.start();
public class MainActivity extends Activity
{
int min, sec;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
min = 5;
sec = 0;
final TextView timer1 = (TextView) findViewById(R.id.timer1);
timer1.setText(min + ":" + sec);
Thread t = new Thread() {
public void run() {
sec-=1;
if (sec<0) {
min-=1;
sec=59;
}
timer1.setText(min + ":" + sec);
try
{
sleep(1000);
}
catch (InterruptedException e)
{}
}
};
t.start();
}
}
This is a code for a Thread in Java but it doesn't work. Can you help me?
Its a Timer that counts down from 5 Minutes to 0:00.
In your case you are using threads. So you cannot update ui from the thread other than the ui thread. SO you use runOnUithread. I would suggest you to use a countdown timer or a Handler.
1.CountDownTimer
http://developer.android.com/reference/android/os/CountDownTimer.html
Here's a link to another example. Suggest you to check the link for the count down timer.
Countdowntimer in minutes and seconds
Example:
public class MainActivity extends Activity {
Button b;
TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.textView1);
b= (Button) findViewById(R.id.button1);
b.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
startTimer(200000);
}
});
}
private void startTimer(long time){
CountDownTimer counter = new CountDownTimer(30000, 1000){
public void onTick(long millisUntilDone){
Log.d("counter_label", "Counter text should be changed");
tv.setText("You have " + millisUntilDone + "ms");
}
public void onFinish() {
tv.setText("DONE!");
}
}.start();
}
}
2.You can use a Handler
Example :
Handler m_handler;
Runnable m_handlerTask ;
int timeleft=100;
m_handler = new Handler();
m_handlerTask = new Runnable()
{
#Override
public void run() {
if(timeleft>=0)
{
// do stuff
Log.i("timeleft",""+timeleft);
timeleft--;
}
else
{
m_handler.removeCallbacks(m_handlerTask); // cancel run
}
m_handler.postDelayed(m_handlerTask, 1000);
}
};
m_handlerTask.run();
3.Timer
Timer runs on a different thread. You should update ui on the ui thread. use runOnUiThread
Example :
int timeleft=100;
Timer _t = new Timer();
_t.scheduleAtFixedRate( new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() //run on ui thread
{
public void run()
{
Log.i("timeleft",""+timeleft);
//update ui
}
});
if(timeleft>==0)
{
timeleft--;
}
else
{
_t.cancel();
}
}
}, 1000, 1000 );
You are trying to update the UI Thread from a background Thread with
timer1.setText(
which you can't do. You need to use runOnUiThread(), AsyncTask, CountDownTimer, or something similar.
See this answer for an example of runOnUiThread()
But CountDownTimer is nice for things like this.
Also, when posting a question on SO, statements like "it doesn't work." are very vague and often unhelpful. Please indicate the expected results compared to actual results of your code and logcat if the app is crashing.