Update UI from an event with a thread - android

Im working on a small application to try out an idea that I have. The idea is to periodically update the UI when event of some sort occurs. In the demo I've created, I'm updating a ProgressDialog every 2 seconds for 15 turns.
The problem I am having, which I don't quite understand is that when an event is handled, I send a message to the handler which is supposed to update the message in the ProgressDialog. When this happens however, I get an exception which states that I can't update the UI from that thread.
The following code appears in my Activity:
ProgressDialog diag;
String diagMessage = "Started loading...";
final static int MESSAGE_DATA_RECEIVED = 0;
final static int MESSAGE_RECEIVE_COMPLETED = 1;
final Handler handler = new Handler(){
#Override
public void handleMessage(Message msg){
diag.setMessage(diagMessage);
switch(msg.what){
case MESSAGE_DATA_RECEIVED:
break;
case MESSAGE_RECEIVE_COMPLETED:
dismissDialog();
killDialog();
break;
}
}
};
Boolean isRunning = false;
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupDialog();
if(isRunning){
showDialog();
}
setContentView(R.layout.main);
}
void setupDialog(){
if(diag == null){
diag = new ProgressDialog(ThreadLoading.this);
diag.setMessage(diagMessage);
}
}
void showDialog(){
isRunning = true;
if(diag != null && !diag.isShowing()){
diag.show();
}
}
void dismissDialog(){
if(diag != null && diag.isShowing()){
diag.dismiss();
}
}
void killDialog(){
isRunning = false;
}
public void onStart(){
super.onStart();
showDialog();
Thread background = new Thread(new Runnable(){
public void run(){
try{
final ThreadRunner tr = new ThreadRunner();
tr.setOnDataReceivedListener(new ThreadRunner.OnDataReceivedListener(){
public void onDataReceived(String message){
diagMessage = message;
handler.handleMessage(handler.obtainMessage(MESSAGE_DATA_RECEIVED));
}
});
tr.setOnDataDownloadCompletedEventListener(new ThreadRunner.OnDataDownloadCompletedListener(){
public void onDataDownloadCompleted(String message){
diagMessage = message;
handler.handleMessage(handler.obtainMessage(MESSAGE_RECEIVE_COMPLETED));
}
});
tr.runProcess();
}
catch(Throwable t){
throw new RuntimeException(t);
}
}
});
background.start();
}
#Override
public void onPause(){
super.onPause();
dismissDialog();
}
For curiosity sake, here's the code for the ThreadRunner class:
public interface OnDataReceivedListener {
public void onDataReceived(String message);
}
public interface OnDataDownloadCompletedListener {
public void onDataDownloadCompleted(String message);
}
private OnDataReceivedListener onDataReceivedEventListener;
private OnDataDownloadCompletedListener onDataDownloadCompletedEventListener;
int maxLoop = 15;
int loopCount = 0;
int sleepTime = 2000;
public void setOnDataReceivedListener(OnDataReceivedListener onDataReceivedListener){
this.onDataReceivedEventListener = onDataReceivedListener;
}
public void setOnDataDownloadCompletedEventListener(OnDataDownloadCompletedListener onDataDownloadCompletedListener){
this.onDataDownloadCompletedEventListener = onDataDownloadCompletedListener;
}
public void runProcess(){
for(loopCount = 0; loopCount < maxLoop; loopCount++){
try{
Thread.sleep(sleepTime);
onDataReceivedEventListener.onDataReceived(Integer.toString(loopCount));
}
catch(Throwable t){
throw new RuntimeException(t);
}
}
onDataDownloadCompletedEventListener.onDataDownloadCompleted("Download is completed");
}
Am I missing something? The logic makes sense to me and it looks like everything should work, I'm using a handler to update the UI like it is recommended.
Any help will be appreciated.
Thanks,
Tyrone
P.S. I'm developing for Android 1.5

I found the problem. After comparing my code with someone else's code which was very similar, the following small problem was found:
handler.handleMessage(handler.obtainMessage(MESSAGE_RECEIVE_COMPLETED));
Should actually be:
handler.sendMessage(handler.obtainMessage(MESSAGE_RECEIVE_COMPLETED));
Hopefully someone finds this useful and learns from my mistake :)
Regards,
Tyrone

Related

Handler doesn't work correctly

first of all excuse me if my title doesn't describe my question very well but i couldn't find a better one .
there is a simple stopWatch app that has three button start,stop,reset and a textview to display time . app has just one activity like this:
public class StopwatchActivity extends AppCompatActivity {
private int mNumberOfSeconds = 0;
private boolean mRunning = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stopwatch);
//if if uncomment this runner method and delete the runner inside onClickStart everything will work find
//runner()
}
public void onClickStart(View view){
mRunning = true;
runner();
}
public void onClickStop(View view){
mRunning = false;
}
public void onClickReset(View view){
mRunning = false;
mNumberOfSeconds = 0;
}
public void runner(){
final TextView timeView = (TextView) findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int hours = mNumberOfSeconds/3600;
int minutes = (mNumberOfSeconds%3600)/60;
int second = mNumberOfSeconds%60;
String time = String.format("%d:%02d:%02d" , hours , minutes , second );
timeView.setText(time);
if (mRunning){
mNumberOfSeconds++;
}
handler.postDelayed(this , 1000);
}
});
}
}
my problem is when i comment the runner() in onClickStart method and put it in the onCreate method everything is ok . but when i change the code like above the code is still running but after i press stop button and then press start again the second will increment by 4 or 5 very fast.
can anyone explain me what is the difference between this two modes?
declare your handler globally
public void runner(){
timeView = (TextView) findViewById(R.id.time_view);
handler = new Handler();
runnable = new Runnable() {
#Override
public void run() {
int hours = mNumberOfSeconds/3600;
int minutes = (mNumberOfSeconds%3600)/60;
int second = mNumberOfSeconds%60;
String time = String.format("%d:%02d:%02d" , hours , minutes , second );
timeView.setText(time);
if (mRunning){
mNumberOfSeconds++;
}
handler.postDelayed(this , 1000);
}
}
handler.post(runnable);
}
in button function
public void onClickStart(View view){
if(handler != null) {
//restart the handler to avoid duplicate runnable
handler.removeCallbacks(runnable);//or this handler.removeCallbacksAndMessages(null);
}
mRunning = true;
runner();
}
public void onClickStop(View view){
mRunning = false;
handler.removeCallbacks(runnable); // this will stop the handler from working
}

What is the difference between a UIHandler and a Handler

I want to show numbers from 1 to 100 in sequel order in the TextView and to wait 1 second after printing each number. I also want to implement it using Android services.
I don't know the difference between UIHandler and Handler. When I google about this issue, all I am getting is the difference between handler and a thread.
Please help me out of this,
Thanks in advance
private static final int SHOW_MESSAGE = 1;
private static final int m_cdelay = 1000;
private UIHandler m_cUIHandler;
public int m_cI= 0;
TextView m_cTextShow;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
m_cTextShow = (TextView) findViewById(R.id.textview1);
for(m_cI=1; m_cI <= 100; m_cI++){
//m_cUIHandler = new UIHandler();
//m_cUIHandler.sendEmptyMessageDelayed(SHOW_MESSAGE, 1000);
showMessage(m_cI);
}
}
private void showMessage(int m_cI2) {
for(m_cI=1; m_cI <= 100; m_cI++){
m_cTextShow.setText(""+m_cI);
new Thread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(m_cdelay);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}).start();
}
}
#Override
protected void onResume() {
super.onResume();
startService(new Intent(this, NumberService.class));
}
public final class UIHandler extends Handler {
#Override
public void handleMessage(Message pObjMessage) {
switch(pObjMessage.what) {
case SHOW_MESSAGE:
m_cTextShow.setText(""+m_cI);
break;
}
}
}
You can actually rewrite your code like that to make it probably work.
Pls test it and respond.
public void showMessage(int number){
runOnUiThread(new Runnable(){
public void run() {
//Write your number onto the screen
}
});
}
protected void onCreate(Bundle savedInstanceState) {
//Blablabla...
for(m_cI=1; m_cI <= 100; m_cI++){
//m_cUIHandler = new UIHandler();
//m_cUIHandler.sendEmptyMessageDelayed(SHOW_MESSAGE, 1000);
showMessage(m_cI);
Thread.sleep(1000);
}
}

Android: Pause and resume a thread within an activity

EDIT: I've found that what I'm describing below only occurs on my emulated device (Nexus 5, target api 19, 4.4.2 with Intel Atom (x86) cpu), but NOT on my physical device (HTC One)....
EDIT2: Edit1 was due to an IllegalStateException that I didnt catch. Added some code to check if the thread was already running before trying to start it. This combined with the accepted answer resolved my issue.
I have implemented an activty that starts a new thread in the activity's onCreate method, like this:
...
private boolean running;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
running = true;
new Thread(null, work, "myThread").start();
}
Runnable work = new Runnable() {
#Override
public void run() {
while (running) {
//Doing work
}
}
};
I'm "pausing" my thread with my activity's onPause method, like this:
#Override
protected void onPause() {
running = false;
super.onPause();
}
So I thought that resuming it would be just as easy...ยจ
#Override
protected void onResume(){
running = true;
super.onResume();
}
but my thread isn't resuming. Any ideas why? Thankful for any help.
Marcus
All of the answers i think have some issues about your running variable because you can not write and read a variable from two different Threads without synchronized block so i post my own answer:
package com.example.threadandtoast;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
public class MonitorObject{
public boolean running = true;
public String message = "";
public boolean mustBePost = true;
}
Thread t;
int threadNameCounter = 0; // i use this variable to make sure that old thread is deleted
// when i pause, you can see it and track it in DDMS
Runnable work = new Runnable() {
boolean myRunning;
#Override
public void run() {
synchronized(mSync) {
myRunning = mSync.running;
}
while (myRunning) {
runOnUiThread(new Runnable() { // in order to update the UI (create Toast)
#Override // we must switch to main thread
public void run() {
// i want to read the message so i must use synchronized block
synchronized(mSync) {
// i use this variable to post a message just for one time because i am in an infinite loop
// if i do not set a limit on the toast i create it infinite times
if(mSync.mustBePost){
Toast.makeText(MainActivity.this, mSync.message, Toast.LENGTH_SHORT).show();
// the message post so i must set it to false
mSync.mustBePost = false;
// if i am going to pause set mSync.running to false so at the end of infinite loop
//of thread he reads it and leaves the loop
if(mSync.message.equals("Main Activity is going to pause")){
mSync.running=false;
}
}
}
}
});
synchronized(mSync) {
myRunning = mSync.running;
}
}
}
};
final MonitorObject mSync = new MonitorObject();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onPause() {
super.onPause();
synchronized(mSync) {
// mSync.running = false; you can not set it here because
// it is possible for the thread to read it and exit the loop before he posts your message
mSync.mustBePost=true;
mSync.message = "Main Activity is going to pause";
}
}
#Override
protected void onResume(){
super.onResume();
threadNameCounter++;
synchronized(mSync) {
mSync.running = true;
mSync.mustBePost=true;
mSync.message = "Main Activity is going to resume";
}
t = new Thread(work,"My Name is " + String.valueOf(threadNameCounter));
t.start();
}
}
Or you can use this code:
public class MainActivity extends ActionBarActivity {
Thread t;
int threadNameCounter = 0; // i use this variable to make sure that old thread is deleted
// when i pause, you can see it in DDMS
String message = "";
boolean isPost = false;
Runnable work = new Runnable() {
#Override
public void run() {
while (true) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if(!isPost){
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
isPost = true;
if( message.equals("Main Activity is going to pause")){
t.interrupt();
}
}
}
});
if(Thread.currentThread().isInterrupted()){
break;
}
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onPause() {
super.onPause();
message = "Main Activity is going to pause";
isPost = false;
}
#Override
protected void onResume(){
super.onResume();
message = "Main Activity is going to resume";
isPost = false;
threadNameCounter++;
t = new Thread(work,"My Name is " + String.valueOf(threadNameCounter));
t.start();
}
}
you can also use semaphore or wait-notify approach.
i put public String message = ""; and public boolean mustBePost = true; in to mSync object but it is
not necessary because only main thread have an access to them.
if you have any problem please ask.
The statement running = false; will stop execution of the Thread, instead of pausing it. Use two variables: One for stopping current Thread, and another for pausing and resuming the Thread, as follow:
boolean isThreadPause=false;
Runnable work = new Runnable() {
#Override
public void run() {
while (running) {
if (!isThreadPause) {
// Doing work
}
}
}
};
In the onPause event of the Activity, set isThreadPause to true, and in the onResume event, set isThreadPause to false.
This is because your Runnable object stops when the while loop stops. You could try this:
Runnable work = new Runnable() {
#Override
public void run() {
while () {
if(running){
//Doing work
}
}
}
};

ViewRoot$CalledFromWrongThreadException on android app

I have read some threads regarding this and I did already take steps to resolve it.
I am using a handler (so that I don't update the UI on a separate thread) and so far I can't understand why this is still happening.
public class MyApp extends Activity implements OnClickListener, Runnable {
private ViewSwitcher switcher;
private static final int REFRESH_SCREEN = 1;
private boolean isValid = false;
private ProgressDialog dialog;
private TextView errorMessage;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button)findViewById(R.id.button1);
button.setOnClickListener(this);
TextView errorMessage = (TextView)findViewById(R.id.txtErrorMessage);
errorMessage.setVisibility(View.GONE);
switcher = (ViewSwitcher) findViewById(R.id.profileSwitcher);
}
public void onClick(View v)
{
isValid = false;
dialog = ProgressDialog.show(ConcentraApp.this, "", "Loading. Please wait...", true);
Thread thread = new Thread(this);
thread.start();
}
public void run() {
String username = ((TextView)findViewById(R.id.txtUsername)).getText().toString();
String password = ((TextView)findViewById(R.id.txtPassword)).getText().toString();
errorMessage = (TextView)findViewById(R.id.txtErrorMessage);
errorMessage.setVisibility(View.GONE);
/* ... contact web service and get response ..*/
try {
/* get result from web service */
isValid = Boolean.parseBoolean(result);
if(isValid)
{
handler.sendEmptyMessage(1);
}
else
{
handler.sendEmptyMessage(0);
}
} catch (Exception e) {
handler.sendEmptyMessage(2);
isValid = false;
}
}
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if(msg.what == 1)
{
errorMessage.setVisibility(View.VISIBLE);
errorMessage.setText("Correct login");
switcher.showNext();
}
else if(msg.what == 0)
{
errorMessage.setVisibility(View.VISIBLE);
errorMessage.setText("Invalid login");
}
else
{
errorMessage.setVisibility(View.VISIBLE);
errorMessage.setText("Internet error");
}
dialog.dismiss();
}
};
}
I am very new to this so I wouldn't be surprised if I'm missing something obvious.
It works fine without the thread, but then the process dialog doesn't show.
Many thanks in advance
You cannot call this:
errorMessage.setVisibility(View.GONE);
From a background thread. You should do it via handler as well.

Android Thread Exception?

i got thread exception in android , what i intend to do is, while clicking a button i started a thread going to dynamically invoke the handler ,handler update the text view with integer value , while reaching integer 10, i going to stop the thread and have to show an alert ,but it will cause an error, what i possibly doing is shown below
public class sample extends Activity implements Runnable{
public Camcorder()
{
try{
counterThread = new Thread(this);
}catch(Exception ee)
{
}
}
public void run()
{
try{
while(counterFlag)
{
System.out.println("The time starts at : "+counter);
Thread.sleep(1000);
calculate(counter);
counter++;
}
}catch(Exception ee){
System.out.println("Err in ee : "+ee);
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
c=this.getApplicationContext();
requestWindowFeature(Window.FEATURE_NO_TITLE);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setContentView(R.layout.main);
authalert3 = new AlertDialog.Builder(this);
authalert3.setTitle("Save Video");
authalert3.setMessage("Do you want to save this Video?");
authalert3.setPositiveButton("Yes", null);
Button test = (Button) findViewById(R.id.widget33);
test.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
counter = 0;
counterFlag = true;
counterThread.start();
}
});
public void calculate(int counter2) {
// TODO Auto-generated method stub
if(counter2<60){
if(counter2<10)
{
smin="0"+counter2;
}
else{
smin=""+counter2;
}
}
else{
hours++;
counter=0;
smin="00";
if(hours<10){
shours="0"+hours;
}
else{
shours=""+hours;
}
}
handler.sendEmptyMessage(0);
}
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
String tes=shours+":"+smin;
time.setText(tes);
test();
};
};
public void test(){
duration=1;
if(duration==hours){
counterFlag = false;
videoPath=camcorderView.stopRecording();
authalert3.create().show();
counterThread.stop();
}
}
the error is thrown at counterThread.stop();
Anyone suggest me , how to solve this error.
You don't stop threads by calling counterThread.stop. This method is deprecated. In your case, by setting counterFlag = false; your thread should be stopping itself.
You will also be getting an exception if you click twice on your button: you cannot call start on a Thread that has already been started. You must create a new instance of that Thread and start that new instance (stop the old instance before if necessary).
You can see that SO answer for some sample code on how to create/stop threads: Android thread in service issue. I suggest that you also read some tutorial on Java Threads (this is not specific to Android).
Additionally I think that you don't need a thread at all, you are doing nothing complicated and thus you could simply use the handler to do all the work:
private static final int MSG_REFRESH_UI = 0;
private static final int MSG_UPDATE_COUNTER = 1;
private int counter = 0;
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
if (msg.what==MSG_REFRESH_UI) {
String tes=shours+":"+smin;
time.setText(tes);
test();
} else if (msg.what==MSG_UPDATE_COUNTER) {
counter++;
if (counter<10) {
calculate(counter);
handler.sendEmptyMessageDelayed(MSG_UPDATE_COUNTER, 1000);
handler.sendEmptyMessage(MSG_REFRESH_UI);
}
}
};
};
public void onResume() {
handler.sendEmptyMessage(MSG_UPDATE_COUNTER);
}
public void calculate(int counter2) {
if (counter2<10) {
smin = "0"+counter2;
} else if (counter2<60) {
smin = ""+counter2;
} else{
hours++;
counter=0;
smin="00";
if(hours<10){
shours="0"+hours;
} else {
shours=""+hours;
}
}
}
This will stop the thread at 10
while(counterFlag)
{
System.out.println("The time starts at : "+counter);
Thread.sleep(1000);
calculate(counter);
counter++;
if(counter == 10) counterFlag = false;
}

Categories

Resources