Thursday, June 28, 2012

How to update Activity from Service using ResultReceiver

Today I am going to show how can we update Activity using android.os.ResultReceiver. What we need is just create and inner class inside an Activity that extends ResultReceiver and override its onReceiveResult() methods that will be called while sending data from Service class and inside this method we can update UI components.


What I will show in Demo?
I will just create an Activity with a TextView and update the TextView with current seconds of time.


So, create an Activity with main.xml having a TextView. Also, an inner class that extends ResultReceiver and  a class that extends Runnable to be used for runOnUiThread.



public class ResultReceiverDemoActivity extends Activity{

Intent intent;
TextView txtview;
MyResultReceiver resultReceiver;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

resultReceiver = new MyResultReceiver(null);
txtview = (TextView) findViewById(R.id.txtview);
intent = new Intent(this, MyService.class);
intent.putExtra("receiver", resultReceiver);
startService(intent);
}

@Override
protected void onDestroy() {
super.onDestroy();
stopService(intent);
}


class UpdateUI implements Runnable
{
String updateString;

public UpdateUI(String updateString) {
this.updateString = updateString;
}
public void run() {
txtview.setText(updateString);
}
}

class MyResultReceiver extends ResultReceiver
{
public MyResultReceiver(Handler handler) {
super(handler);
}

@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {

if(resultCode == 100){
runOnUiThread(new UpdateUI(resultData.getString("start")));
}
else if(resultCode == 200){
runOnUiThread(new UpdateUI(resultData.getString("end")));
}
else{
runOnUiThread(new UpdateUI("Result Received "+resultCode));
}
}
}
}

As, you can see there is nothing much in the above code just a simple one. The new thing about you would be the class that extends ResultReceiver. It is having an overrided method onReceiveResult(int resultCode, Bundle resultData) with parameters resultCode and Bundle. These two parameters we will pass from the Service class using send(resultCode, Bundle resultData) of ResultReceiver which will be pass to onReceiveResult(int resultCode, Bundle resultData) where we can update the UI.

Also, one more important thing is that you might have seen that I am passing a putExtra to Service class as
intent.putExtra("receiver", resultReceiver); where resultReceiver is the instance of MyResultReceiver class that extends ResultReceiver. We will get the putExtra in the Service class and use the same instance to send data from Service to Activity using send(resultCode, Bundle resultData).


Now, lets add the Service class that is also a simple one having a Timer with 1 second.



public class MyService extends Service{

Timer timer = new Timer();
MyTimerTask timerTask;
ResultReceiver resultReceiver;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

resultReceiver = intent.getParcelableExtra("receiver");

timerTask = new MyTimerTask();
timer.scheduleAtFixedRate(timerTask, 1000, 1000);
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
timer.cancel();
Bundle bundle = new Bundle();
bundle.putString("end", "Timer Stopped....");
resultReceiver.send(200, bundle);
}

class MyTimerTask extends TimerTask
{
public MyTimerTask() {
Bundle bundle = new Bundle();
bundle.putString("start", "Timer Started....");
resultReceiver.send(100, bundle);
}
@Override
public void run() {
SimpleDateFormat dateFormat = new SimpleDateFormat("s");
resultReceiver.send(Integer.parseInt(dateFormat.format(System.currentTimeMillis())), null);
}
}
}

So, simply we are getting the putExtra of ResultReceiver's instance using resultReceiver = intent.getParcelableExtra("receiver"); inside onStartCommand() to use it further for sending the data to Activity. You can send any data to Activity using send(resultCode, Bundle resultData) method of ResultReceive, you can send an error message also with the same method checking the resultCode.
Source code can be found here.

4 comments:

  1. Thanks for the tute. Could you please explain why you would use this method instead of Handlers and Messages?

    ReplyDelete
    Replies
    1. Yes, agree with El Niño, your tutorial is Pretty good.

      If one needs to use the Handler <-> Message way to achieve the same goal, then here it goes:

      ----
      In your UI Class, you must define another nested static class which extends the Handler Class, like:

      public static class TestHandler extends Handler
      {
      @Override
      public void handleMessage(Message msg)
      {
      super.handleMessage(msg);
      switch(msg.what) // msg.what is an identifier (int)
      {
      // Do your stuff for each message received
      }
      }
      }

      Then, in your Service Class, you instantiate that static class as a Handler:
      (You can use the specific class, like: MyUI.TestHandler, or you can just do a simple Uppercast to use a Handler in a straightforward manner.)

      Like:

      public class BackgroundService extends Service
      {

      @Override
      public int onStartCommand(Intent intent, int flags, int startId)
      {
      Handler hand = new MyUI.TestHandler();

      Message msg = new Message();
      msg.what = MyUI.sServAtivo; // 1
      hand.sendMessageDelayed(msg, 4000); // Just to have to wait

      return super.onStartCommand(intent, flags, startId);
      }

      @Override
      public IBinder onBind(Intent arg0)
      {
      return null;
      }

      }

      ---


      Just bear in mind:
      If you do it this way, you cannot directly access UI components, "only the UI can touch its views" someone may say. If you need to, you'll have to implement some listeners or, maybe, extend your UI activity and implement the changes inside another Thread.

      Delete