43

I am using an IntentService to handle network communications with a server via JSON. The JSON/server part is working fine, but I'm having trouble getting the results back where they are needed. The following code shows how I am starting the intent service from inside onClick(), and then having the service update a global variable to relay the results back to the main activity.

public class GXActivity extends Activity {

    private static final String TAG = "GXActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // === called when the activity is first created

        Log.i(TAG, "GXActivity Created");

        super.onCreate(savedInstanceState);
        setContentView(R.layout.start);

        View.OnClickListener handler = new View.OnClickListener() {
            public void onClick(View v) {

                // === set up an application context to hold a global variable
                // === called user, to contain results from the intent service
                GXApp appState = ((GXApp) getApplicationContext());
                User user = new User(-1); // initialize user id to -1
                appState.setUser(user);

                // === next start an intent service to get JSON from the server;
                // === this service updates the user global variable with
                // === results from the JSON transaction
                Intent intent = new Intent(this, GXIntentService.class);
                startService(intent);

                // === check app context for results from intent service
                appState = ((GXApp) getApplicationContext());
                if (appState.getUser().getId() != -1)...

            }
        }
    }
}

The problem I'm having is that the intent service that parses the JSON doesn't get invoked until after onCreate() completes, so my code that's looking for the results is stuck looking at uninitialized results.

What should I do differently so the intent service gets called before I check the results? Would it work if I pulled the click listener out of the onCreate() function? Is there a another/better to structure this code? Many thanks.

Nikhil Agrawal
  • 26,128
  • 21
  • 90
  • 126
gcl1
  • 4,070
  • 11
  • 38
  • 55

4 Answers4

86

You should look at creating your own ResultReceiver subclass in your activity. ResultReceiver implements Parcelable so can be passed from your Activity to your Service as an extra on the Intent.

You'll need to do something like this:

  1. Implement a subclass of ResultReceiver within your activity class. The key method to implement is onReceiveResult(). This method provides you a with Bundle of result data which can be used to pass whatever information you are retrieving in your Service. Simply unpack the data you need and use it to update your activity.

  2. In your activity, create a new instance of your custom ResultReceiver and add it to the Intent you use to start your service.

  3. In your Service's onStartCommand() method, retrieve the ResultReceiver passed in on the Intent and store it in a local member variable.

  4. Once your Service completes its work, have it call send() on the ResultReceiver passing whatever data you want to send back to the activity in a Bundle.

This is a pretty effective pattern and means you're not storing data in nasty static variables.

tomato
  • 3,373
  • 1
  • 25
  • 33
  • Thanks for the quick reply! Two follow up questions. First, if I switch to using intent extras as you suggested, is there a good, simple code example of the pattern you outlined? Second, if I wanted to stick with the global variable approach (in this case, fairly innocuous, as the data it holds does not change very much or very often), how would I do it? Is there a way to pull the click listener out of onCreate(), or otherwise synchronize the Activity and Service? – gcl1 Apr 26 '12 at 14:18
  • The fundamental problem you have is that the Service runs completely independently of the Activity and takes an indeterminate amount of time to complete. The "correct" solution to this problem is using some kind of asynchronous callback mechanism (which is what the ResultReceiver does for you). The alternative is to repeatedly poll some sort of `isRunning` static variable in your activity to detect when the service is finished and then read the data from another static variable. That's pretty inefficient though and probably requires a dedicated background thread - not recommended. – tomato Apr 26 '12 at 14:29
  • I tried, but just started here and apparently don't have enough reputation points yet. But your answer was excellent, and cracked the nut that kept me up til 3 am last night. Thanks! – gcl1 Apr 27 '12 at 01:15
  • 3
    What happens in this case when the Activity is no longer alive when the result calls `send()` on the `ResultReceiver'? – Dheeraj Vepakomma Jun 17 '15 at 05:28
  • This is a nice solution. previously I was writing interfaces. – Prashanth Debbadwar Jul 07 '15 at 07:15
  • 1
    Let's say I start the `IntentService` with `Activity A` passing the `ResultReceiver` in the bundle. Now when the `IntentService` is executing, I rotate the device, `Activity A` is killed and is recreated. Then `IntentService` is done with the task and calls `send()` on the `ResultReceiver`. But the original activity no longer exists. How do you handle this? How does this pattern handles `orientation change` ? – Henry Dec 20 '15 at 13:22
  • 2
    I have similar requirement and come into this thread. Is there any answer for the concern that the Activity is destroyed and re-created? – Gordon Liang Dec 23 '15 at 02:43
  • 1
    Correct me if I am wrong but the ResultReceiver won't work if the original Activity is destroyed and re-created (via device rotation for example)? – Micro Jan 02 '16 at 20:38
  • I suppose you could implement `onRetainNonConfigurationInstance()` in your Activity and use that to pass the ResultReceiver to the new Activity instance. – Edward Falk Mar 19 '16 at 22:51
  • My own question: can you use ResultReceiver to pass data between activities, between services, or from Activity to Service? – Edward Falk Mar 19 '16 at 22:52
12

There's many ways to do stuffs in background getting back a result (AsyncTask, bind an Activity to a Service...), but if you want to mantain your IntentService's code you can simply:

1- send a broadcast intent (that contains the status in its extended data) at the end of the work in your IntentService

2- implement a LocalBroadcastReceiver in your Activity that consumes the data from the Intent

This is also the recommended way in the official documentation (if you want to mantain your IntentService):

https://developer.android.com/training/run-background-service/report-status.html

DSoldo
  • 959
  • 10
  • 19
7

If all you need to do is get a JSON from the server without locking up the UI thread, it may be simpler to just use AsyncTask.

Yusuf X
  • 14,513
  • 5
  • 35
  • 47
  • 2
    async-task life cycle is bound to the thread it's running so if i rotate the screen it will be relaunched!!! – hadi Nov 05 '15 at 15:09
1

You can use Otto library. Otto is an Open Source project designed to provide an event bus implementation so that components can publish and subscribe to events.

Prashanth Debbadwar
  • 1,047
  • 18
  • 33