Sunday, 22 April 2012

IntentService: Providing data back to Activity #android

It is advisable to use thread/asynTask or Service to handle long running task such as file I/O, internet access etc. IntentService is simple, easy to use and take care of many hectic tasks for You.Read more about IntentService at
developer.android . A simple use case of the topic can be:
  1. Your activity send a web request to IntentService to process.
  2.  Your IntentService execute that request using DefaultHttpClient.
  3.  And Return results (whatever, true,false list of objects/records etc) back to the calling activity.
Now the task is to return the results back to the activity. There are two options available either we can use Broadcast receiver or ResultReceiver.
  • Broadcast should be used if you want to send data/notifications across applications, whenever you send broadcast its sent system wide read more about broadcast receivers at developer.android.
  •  Result receiver is an interface you implement and pass it to the intentService through putExtra. IntentService then fetch this object and call its receiver.send function to send anything (in bundle) to calling activity[who started the intentservice]. Result receiver has preference over broadcast receivers if your all communication is internal to your application.

Lets start, First we need to have our own receiver class extended from ResultReceiver and containing Receiver Interface. Lets say its MyResultReceiver:

package sohail.aziz.service;

import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;

public class MyResultReceiver extends ResultReceiver {

    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
        // TODO Auto-generated constructor stub
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);

    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

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

        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }

}



Now we implements Receiver interface [defined in MyResultReceiver] in our Activity. Lets say this LoginActivity :

package sohail.aziz.view;
import sohail.aziz.service.MyResultReceiver;
import sohail.aziz.service.MyIntentService;
import sohail.aziz.service.MyResultReceiver.Receiver;


public class LoginActivity extends Activity implements OnClickListener,Receiver {
        Button btLogin;   
        Public MyResultReceiver mReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login);

        btLogin = (Button) findViewById(R.id.btLogin);
        mReceiver = new MyResultReceiver(new Handler());

        mReceiver.setReceiver(this);
        btLogin.setOnClickListener(this);
        
    }

    @Override
    public void onClick(View arg0) {
        // TODO Auto-generated method stub
                 
             if(arg0.getId()==R.id.btLogin){
                  Intent i = new Intent(this, MyIntentService.class);
                  i.putExtra("nameTag","sohail" );
                  i.putExtra("receiverTag", mReceiver);
                  startService(i);
               
              }


    }
    
    @Override
    public void onReceiveResult(int resultCode, Bundle resultData) {
        // TODO Auto-generated method stub
        
                 Log.d("sohail","received result from Service="+resultData.getString("ServiceTag"));

    }
}




And here is the MyService class :

import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.Log;

public class MyIntentService extends IntentService {

    
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // TODO Auto-generated method stub

        
        ResultReceiver rec = intent.getParcelableExtra("receiverTag");
                String recName= intent.getString("nameTag");
                Log.d("sohail","received name="+recName);

        Log.d("sohail","sending data back to activity");

                Bundle b= new Bundle();
                b.putString("ServiceTag","aziz");
        rec.send(0, b);
    }

}



Whenever we call startService , intentService is started if its not been running, and if its already running it put our (new)request in a queue and execute it when it finishes running request. When we call rec.send() , onReceiveResult is called and the value we passed in bundle is received in the activity.


Is it helpful for you?

9 comments:

  1. Hi. I am currently evaluating this patter for Activity - IntentService Communication. I only have two questions if I may ask:

    How do you control the Activity's Receiver on device rotation? Do you have to save the Receiver in the Activity's state Bundle and retrieve it upon activity recreation?

    If your Intent Service is working and you close up the Activity and enter again, what would you do to handle the "working" state on the activity? Example app: Lets say you want to update all your feeds on an app, you hit the refresh button (IntentService starts with Activity´s receiver), Close the app (Activity gets destroyed but IntentService keeps working), return to the App (New Activity created and IntentService is still working)

    Thanks for your time. Nice Tutorial.

    ReplyDelete
    Replies
    1. there can be many alternatives but why don't you consider using LocalBroadcast manager pattern http://www.sohailaziz.com/2012/04/localbroadcastmanager-intra-application.html.

      Delete
  2. Hi Sohail,

    Thanks for the great sum up. I have a question. I am using the above scheme to implement a login. After the service executes the network operation, onReceiveResult method in the LoginActivity runs in the service thread, not the main thread, so i cannot update the ui in the login screen. I get the error "Only the original thread that created a view hierarchy can touch its views."

    Only difference in my implementation from the one you provided here is that, I did not extend the ResultReceiver and implemented it in my Activity. I simply attached a new receiver to my intent as:

    intent.putExtra("receiver", new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {

    Log.d("login activity rec.", "received something");
    Log.d("login activiy rec.", String.valueOf(resultCode));
    if (resultCode == 200) {
    Log.d("login activity rec.", "received login result");
    showProgress(false);
    }
    }
    });

    The showProgress(false) call in the callback fails. Do you have any suggestions regarding to sending the result to the main thread?

    Thanks

    ReplyDelete
    Replies
    1. Why not use Activity.runOnUiThread() to update the UI?

      Delete
  3. Welcome. It seems you are making mistake of not implementing the result receiver in activity. How can it be called in activity (UI thread) if you don't implement it on UI thread?
    Try executing my code above and let me know.

    ReplyDelete
  4. Thank you very much for this tutorial. I got up and running very quickly following your advice.

    ReplyDelete
  5. ~"whenever you send broadcast its sent system wide"
    Not true for LocalBroadcast

    ReplyDelete
  6. Did you forget to declare the intent service in the AndroidManifest of the app?

    ReplyDelete