15

I have an Activity that uses fragments. These fragments may come and go, based on the users interactions. Many of these fragments launch jobs to an IntentService, which get to run async this way. How should the IntentService report back the results of these jobs?

The fragment that started the job may of may not be present. If a job finishes and the starting fragment is currently active, then it should get notified about this, and act accordingly. If it's not, then no action is needed.

I've thought about using broadcast intents and BroadcastReceiver components, but fragments can't register receivers, only activities.

What solution would you suggest?

Zsombor Erdődy-Nagy
  • 16,864
  • 16
  • 76
  • 101

1 Answers1

10

I noticed the same problem in IOSched App (Google I/O App for Android).

They created DetachableResultReceiver, which extends SDK class ResultReciever.

And they easily use it in Fragments.

Receiver:

/**
 * Proxy {@link ResultReceiver} that offers a listener interface that can be
 * detached. Useful for when sending callbacks to a {@link Service} where a
 * listening {@link Activity} can be swapped out during configuration changes.
 */
public class DetachableResultReceiver extends ResultReceiver {
    private static final String TAG = "DetachableResultReceiver";

    private Receiver mReceiver;

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

    public void clearReceiver() {
        mReceiver = null;
    }

    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);
        } else {
            Log.w(TAG, "Dropping result on floor for code " + resultCode + ": "
                    + resultData.toString());
        }
    }
}

Activity and Fragment:

public class HomeActivity extends BaseActivity {
    private static final String TAG = "HomeActivity";

    private SyncStatusUpdaterFragment mSyncStatusUpdaterFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //...

        mTagStreamFragment = (TagStreamFragment) fm.findFragmentById(R.id.fragment_tag_stream);

        mSyncStatusUpdaterFragment = (SyncStatusUpdaterFragment) fm
                .findFragmentByTag(SyncStatusUpdaterFragment.TAG);
        if (mSyncStatusUpdaterFragment == null) {
            mSyncStatusUpdaterFragment = new SyncStatusUpdaterFragment();
            fm.beginTransaction().add(mSyncStatusUpdaterFragment,
                    SyncStatusUpdaterFragment.TAG).commit();

            triggerRefresh();
        }
    }

    private void triggerRefresh() {
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class);
        intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, mSyncStatusUpdaterFragment.mReceiver);
        startService(intent);

        if (mTagStreamFragment != null) {
            mTagStreamFragment.refresh();
        }
    }

    /**
     * A non-UI fragment, retained across configuration changes, that updates its activity's UI
     * when sync status changes.
     */
    public static class SyncStatusUpdaterFragment extends Fragment
            implements DetachableResultReceiver.Receiver {
        public static final String TAG = SyncStatusUpdaterFragment.class.getName();

        private boolean mSyncing = false;
        private DetachableResultReceiver mReceiver;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
            mReceiver = new DetachableResultReceiver(new Handler());
            mReceiver.setReceiver(this);
        }

        /** {@inheritDoc} */
        public void onReceiveResult(int resultCode, Bundle resultData) {
            HomeActivity activity = (HomeActivity) getActivity();
            if (activity == null) {
                return;
            }

            switch (resultCode) {
                case SyncService.STATUS_RUNNING: {
                    mSyncing = true;
                    break;
                }
                //...
            }

            activity.updateRefreshStatus(mSyncing);
        }

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            ((HomeActivity) getActivity()).updateRefreshStatus(mSyncing);
        }
    }
}
MasterAM
  • 16,283
  • 6
  • 45
  • 66
AlexKorovyansky
  • 4,873
  • 5
  • 37
  • 48
  • 1
    For whatever reason the link to DetachableResultReceiver is not working for me, this works if anyone needs it: http://code.google.com/p/iosched/source/search?q=+DetachableResultReceiver&origq=+DetachableResultReceiver&btnG=Search+Trunk – StackOverflowed Sep 16 '12 at 05:53
  • 1
    Can you please explain that how to use it? – Shajeel Afzal Jun 19 '13 at 09:30
  • 4
    Simply linking to documentation and examples does not make a good answer. When these links decay this answer will be nearly useless. Please improve this answer by providing summaries of the documentation you have linked to. – ahsteele Aug 19 '13 at 02:20
  • 2
    Life is moving forward. Links are broken now, and for now I recommend use Otto to solve with problem with charm - http://square.github.io/otto/ – AlexKorovyansky Aug 29 '13 at 12:32