Search code examples
androidandroid-listviewandroid-listfragment

Callback 'magic' in master detail flow


The Eclipse generated master detail flow has some callback magic in the class which extends ListFragment - I say magic because that's what it looks like when you are an Android and a Java noob, all at once :)

Given the code below can someone answer a few questions for me:

  1. What does the onAttach method of the ListFragment do with mCallbacks = (Callbacks) activity?
  2. Which onItemSelected method is called in onListItemClick method of ListFragment, the one which needs implementation or onItemSelected in FragmentActivity?
  3. All these onItemSelected methods take an id of type String (because DummyContent id is a String). If I changed DummyContent id to a long, which onItemSelected methods would I need to change? I tried changing the one in FragmentActivity but this has @Override so I wasn't allowed to :(

Thank you

public class RecordingListFragment extends ListFragment {

    private static final String STATE_ACTIVATED_POSITION = "activated_position";

    private Callbacks mCallbacks = sDummyCallbacks;
    private int mActivatedPosition = ListView.INVALID_POSITION;

    public interface Callbacks {

        public void onItemSelected(String id);
    }

    private static Callbacks sDummyCallbacks = new Callbacks() {
        @Override
        // NEEDS IMPLEMENTATION - i guess????
        public void onItemSelected(String id) {
        }
    };

...

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (!(activity instanceof Callbacks)) {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = sDummyCallbacks;
    }

    @Override
    public void onListItemClick(ListView listView, View view, int position, long id) {
        super.onListItemClick(listView, view, position, id);
        mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
    }

...

}

And one more file...

public class RecordingListActivity extends FragmentActivity
        implements RecordingListFragment.Callbacks {

    private boolean mTwoPane;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recording_list);
        getActionBar().setDisplayHomeAsUpEnabled(true);

        if (findViewById(R.id.recording_detail_container) != null) {
            mTwoPane = true;
            ((RecordingListFragment) getSupportFragmentManager()
                    .findFragmentById(R.id.recording_list))
                    .setActivateOnItemClick(true);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onItemSelected(String id) {
        if (mTwoPane) {
            Bundle arguments = new Bundle();
            arguments.putString(RecordingDetailFragment.ARG_ITEM_ID, id);
            RecordingDetailFragment fragment = new RecordingDetailFragment();
            fragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.recording_detail_container, fragment)
                    .commit();

        } else {
            Intent detailIntent = new Intent(this, RecordingDetailActivity.class);
            detailIntent.putExtra(RecordingDetailFragment.ARG_ITEM_ID, id);
            startActivity(detailIntent);
        }
    }
}

Solution

  • What does the onAttach method of the ListFragment do with mCallbacks = (Callbacks) activity?

    In the onAttach callback the Activity/FragmentActivity(to which the Fragment will be tied/used) is passed to that Fragment instance(through the activity parameter). That line simply casts that Activity to the Callbacks interface to later be used in the Fragment(see below). Your Activity must implement that interface otherwise the code will fail due to the previous if condition. Basically, the code in the onAttach method says: "The Activity where this fragment will be used must implement the Callbacks interface otherwise the code will fail with an IllegalStateException".

    Which onItemSelected method is called in onListItemClick method of ListFragment, the one which needs implementation or onItemSelected in FragmentActivity?

    In a very raw explanation: The RecordingListFragment keeps a reference(the mCallbacks field) to a Callbacks type object(meaning a class which implements the Callbacks interface). Initially, to this Callabacks reference the code will assign a default/empty Callbacks reference(sDummyCallbacks) which doesn't do anything(so no, you don't have to provide some implementation for the sDummyCallbacks) as its onItemSelected is empty, this is to avoid a possible NullPointerException in some cases(for example, if you don't assign something to the mCallbacks field and you call onItemSelected on it). When the onAttach method is run the reference to the Activity where this Fragment will exist will be cast to a Callbaks and put in the mCallbacks field. After this happens, when you call mCallbacks.onItemSelected() the FragmentActivity's onItemSelected method will be called and the code from the method will be run. If at a later point the onDetach is called, the mCallbacks will once again point to the sDummyCallbacks, after this happens, calling mCallbacks.onItemSelected() will do nothing.

    The interface system above is important because it will make your RecordingListFragment a much more reusable component in your code as it will not be tied to a specific Activity implementation. When the user clicks an item in your fragment's list you'll call the onItemSelected on the mCallbacks reference to run the onItemSelected method from that object(the Activity in your case). Your fragment doesn't know how implements the interface and it doesn't even care. Think, for example that you have three activities and each one uses a RecordingListFragment fragment. How you would need to change the RecordingListFragment class to make it work with the three activities where it will be used?

    All these onItemSelected methods take an id of type String (because DummyContent id is a String). If I changed DummyContent id to a long, which onItemSelected methods would I need to change? I tried changing the one in FragmentActivity but this has @Override so I wasn't allowed to :(

    Modify the interface:

    public interface Callbacks {
    
            public void onItemSelected(long id);
    }
    

    If you save the java file Eclipse will complain(that you must override a superclass method) where this interface was implemented. For the sDummyCallbacks:

    private static Callbacks sDummyCallbacks = new Callbacks() {
        @Override
        // NEEDS IMPLEMENTATION - i guess???? <- it doesn't need no implementation
        // it's purpose is to do nothing
        public void onItemSelected(long id) {
        }
    };
    

    Also the FragmentActivity implements the Callbacks interface so you need to change that too:

    public class RecordingListActivity extends FragmentActivity
            implements RecordingListFragment.Callbacks {
    
        @Override
        public void onItemSelected(long id) {
            if (mTwoPane) {
        // ...
    

    finally you would use the mCallbacks in your fragment:

    @Override
    public void onListItemClick(ListView listView, View view, int position, long id) {     
        mCallbacks.onItemSelected(id);
    }
    

    I would recommend that you take some time to study a bit about the java language, so you'll work to understand the Android code and not the Android code + the java way.