2

I have a ContentProvider which fetches data from sqlite database and loads it via Loader. It's updated by a Service which runs an AsyncTask to download data from server and update it in the ContentProvider. I hope it's clear but to be sure:

  • ListFragment takes data from ContentProvider via Loader,

  • ContentProvider gets updated with a Service.

Now, when my local sqlite database is empty the first time I launch the app, it shows that it has no events, even though they're being currently downloaded via Service. I would rather have a ProgressBar shown at this moment (the infinite spinning wheel, not a bar). But if I show a ProgressBar when there are no results from database, it would be there even after fetching data from sever in this specific case when there are no records in the external database (and it occurs quite often in my case). So:

  • When the data is downloaded for the first time by the Service I would like to show a ProgressBar until ContentProvider gives non-empty result OR the Service finished it's job.

  • When ContentProvider returned nothing AND Service finished it's job (and fetched empty result) I would like the app to show "no results found".

My problem is probably: how to notify the ListFragment that the Service is still running or that it finished ts job. I mean - I shouldn't store any reference to the calling Fragment inside the Service. It goes against the idea of ContentProviders, doesn't it? So how?

Note: I don't really know which fragment of code would be helpful here, so if you feel that you need to see some specific frag, just tell me in comments. Thanks!

Michał Klimczak
  • 12,674
  • 8
  • 66
  • 99

2 Answers2

1

There are various techniques how to communicate between Fragment / Activity and a Service.

One of them is using ResultReceiver and sending it to IntentService in Intent extra.

You create custom receiver ServiceResultReceiver extending ResultReceiver.

public class ServiceResultReceiver extends ResultReceiver {

    private Receiver mReceiver;

    public ServiceResultReceiver(Handler handler) {
        super(handler);
    }

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

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

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}

Make your Fragment implement ServiceResultReceiver.Receiver interface. Create receiver and initialize it to your Fragment. You than pass the receiver to service and in service just get the receiver from intent and call receiver.send() to send anything back to the receiver.

public class MyFragment extends ListFragment implements ServiceResultReceiver.Receiver {
    private ServiceResultReceiver mReceiver;
    ....

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        mReceiver = new ServiceResultReceiver(new Handler());
        mReceiver.setReceiver(this);
    }

    public void startMyService() {
        final Intent intent = new Intent(getActivity(), MyService.class);
        intent.putExtra("receiver", mReceiver);
        getActivity().startService(intent);
    }

    @Override
    public void onReceiveResult(int resultCode, Bundle resultData) {
        // service finished
    }
}

public class MyService extends IntentService {
    ...
    @Override
    protected void onHandleIntent(Intent intent) {
        // download data and update database
        ....
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        if (receiver != null) {
            receiver.send(0, null);
        }
    }
}
biegleux
  • 13,179
  • 11
  • 45
  • 52
  • `BroadcastReceiver` seems like an easier solution + my `Service` is not an `IntentService` (or isn't it a problem?), but thank you very much for your answer! – Michał Klimczak Jul 25 '12 at 17:41
  • 1
    `IntentService` is a `Service` with worker thread already implemented, this approach was introduced in google i/o 2010 app. – biegleux Jul 26 '12 at 06:42
  • 1
    One suggested adjustment would be to have the Service hold a WeakReference to the ServiceResultReceiver. As it is drafted above, the Service would hold a reference to the Fragment that started it, preventing garbage collection for the duration of its operation. Although this could be intentional, my guess is it was done just for simplicity of the example. – Nick Campion Dec 18 '12 at 15:18
1

Since you're not so much interested in posting actual progress back to the UI, the simplest way to implement this would be using a pair of custom broadcasts, and maybe a static boolean to show run state as well.

Basically, your service can notify any component of your application that's interested when it is beginning a download and when it has finished it. So you can define two custom action strings:

public static final String ACTION_DOWNLOADSTART = "com.mypackage.intent.ACTION_DOWNLOADSTART";
public static final String ACTION_DOWNLOADCOMPLETE = "com.mypackage.intent.ACTION_DOWNLOADCOMPLETE";

Then have your service broadcast them at the proper points in the code:

Intent start = new Intent(ACTION_DOWNLOADSTART);
sendBroadcast(start);

//...Service does its work

Intent finish = new Intent(ACTION_DOWNLOADCOMPLETE);
sendBroadcast(finish);

You can register for these callbacks anywhere in your application with a BroadcastReceiver and act accordingly (i.e. check the status of the ContentProvider and show/hide progress if necessary).

Another common practice, if you want to be able to check if a Service is running at any given point, is simply to include a private static boolean member that you can toggle when the Service is active (perhaps between onCreate()/onDestroy() but perhaps elsewhere) and an accessor method like isRunning(). Then your application can also check at any time if the Service is running by just calling that method.

devunwired
  • 62,780
  • 12
  • 127
  • 139