onCreateView not called with Fragment in ViewPager

pakerfeldt picture pakerfeldt · Jan 17, 2012 · Viewed 9.5k times · Source

I am using ViewPager from the ACL to show a couple of Fragments. At first, these Fragments where all ListFragments as they contains a list. I'm trying to mimic a 2 column table, so each element in the list contains another list of two objects. I want each cell to be long-clickable (not the entire list item), therefore I implemented my own ColumnLayout (perhaps it should be called CellLayout). Each list item contains two ColumnLayouts which are long-clickable and implements getContextMenuInfo() to return information about which entity was long-clicked. This method looks up the ViewPager, asks for the current fragment and then calls the fragment to return the entity id from the view that was long-clicked. In order to get the correct row the fragment needs to do:

getListView().getPositionForView(v)

And here's where the problem starts. Sometimes getListView() throws IllegalStateException saying "Content view not yet created". I.e. onCreateView(...) hasn't been called. This only happens sometimes when the app is resumed. Today I managed to re-produce the problem by enabling the "Don't keep activities" option in Settings > Developer options on my Galaxy Nexus. I start the app, press Home and then re-open the app from the recent apps menu and now, if I long-click any of the cells in my listview this exception is thrown. By some debug outputs I managed to verify that onCreateView has never been called. Which is kind of strange because the entire ViewPager with its Fragment and its ListView and its cell items are all drawn!

I was told on #android-dev that ListFragment in ACL does not support custom layouts so I re-implemented my android.support.v4.app.Fragment without using ListFragment but that didn't help.

Short summary: I have a custom layout which handles the event of long-pressing the layout for the purpose of creating a MenuInfo object containing information about entitiy in the cell pressed. In order to find the entity contained in the layout, listview.getPositionForView(View v) is eventually called, sometimes causing an IllegalStateException.

Here my custom layout (perhaps there's an easier way to get hold of the ViewPager than digging in the view hierarchy):

package org.remotestick;

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
import android.widget.LinearLayout;

public class ColumnLayout extends LinearLayout {

    public ColumnLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ColumnLayout(Context context) {
        super(context);
    }

    @Override
    protected ContextMenuInfo getContextMenuInfo() {
        int itemTypeId = getChildAt(0).getId();
        int positionTypeId = getId();
        View currentView = this;
        ViewPager viewPager = null;
        while(currentView.getParent() != null) {
            currentView = (View) currentView.getParent();
            if(currentView.getId() == R.id.pager) {
                viewPager = (ViewPager) currentView;
                break;
            } else if (currentView.getId() == R.id.rootLayout)
                break;
        }
        if(viewPager == null)
            return null;

        WorkspaceAdapter adapter = (WorkspaceAdapter) viewPager.getAdapter();
        Pair<Integer, Integer> itemIdAndListPosition = adapter.getItemIdFromWorkspace(viewPager.getCurrentItem(), this, itemTypeId, (positionTypeId == R.id.leftColumn));
        if(itemIdAndListPosition == null)
            return null;
        else
            return new MatrixAdaptableContextMenuInfo(itemTypeId, itemIdAndListPosition.first, itemIdAndListPosition.second);
    }

    public static class MatrixAdaptableContextMenuInfo implements ContextMenuInfo {

        public final int itemTypeId;
        public final Integer itemId;
        public final Integer positionInList;

        public MatrixAdaptableContextMenuInfo(int itemTypeId, Integer itemId, Integer positionInList) {
            this.itemTypeId = itemTypeId;
            this.itemId = itemId;
            this.positionInList = positionInList;
        }

    }
}

And here's (a snippet of) the Fragment:

public class EntityTableFragment extends Fragment implements TelldusEntityChangeListener {

    protected static final String ARG_TITLE = "title";

    protected MatrixAdapter mAdapter;
    protected ProgressViewer mProgressViewer;
    protected MatrixFilter mFilter;
    protected Handler mHandler = new Handler();
    protected RemoteStickData db;

    public boolean onAttach = false;
    public boolean onCreateView = false;

    private ListView listView;

    public static EntityTableFragment newInstance(String title) {
        EntityTableFragment f = new EntityTableFragment();

        Bundle args = new Bundle();
        args.putString(ARG_TITLE, title);
        f.setArguments(args);
        return f;
    }

    @Override
    public void onAttach(SupportActivity activity) {
        super.onAttach(activity);
        onAttach = true;
            try {
            mProgressViewer = (ProgressViewer) activity;
            mAdapter = new MatrixAdapter(getActivity(), mProgressViewer, new ArrayList<List<MatrixEntity>>(), null);
            TelldusLiveService.addListener(this);
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement ProgressViewer");
        }
        db = new RemoteStickData(getActivity());
    }

    @Override
    public void onDetach() {
        super.onDetach();
        TelldusLiveService.removeListener(this);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        listView.setAdapter(mAdapter);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.list, null);
        listView = (ListView) view.findViewById(R.id.list);
        return view;
    }

    @Override
    public String toString() {
        return getArguments().getString(ARG_TITLE);
    }

    public Pair<Integer, Integer> getIdAndPositionForView(View v, boolean isLeft) {
        if(listView == null)
            throw new IllegalStateException("Listview not yet created");
        int positionForView = listView.getPositionForView(v);
        if(isLeft)
            return new Pair<Integer, Integer>(mAdapter.getItem(positionForView).get(0).getId(), positionForView);
        else
            return new Pair<Integer, Integer>(mAdapter.getItem(positionForView).get(1).getId(), positionForView);
    }

Isn't it rather strange that, when resuming the app (if the activity was destroyed), the ViewPager with its Fragments and its ListView and its custom layouts are all drawn. Yet I get IllegalStateException saying the view hasn't been created if I long-press any of the cells in the listview?

edit/ Actually, a Log.d(Constants.TAG, "onCreateView!!!!"); output in onCreateView shows that the methods is invoked two times the first time I start the app (one for each Fragment in the ViewPager) but then it's never called again when the app is resumed!? Is that a bug or what might I be doing wrong?

Answer

pakerfeldt picture pakerfeldt · Jan 19, 2012

The vital part seems to have been the FragmentPagerAdapter (which I didn't include). Debugging shows that getItem() isn't called when the activity is re-created. The Fragments are re-created by other means. Which meant that my implementation of FragmentPagerAdapter had wrong references to the fragments in the FragmentManager.

So instead, when I need to lookup a fragment I use findFragmentByTag() (on the FragmentManager) using the makeFragmentName() to get the appropriate tag name. That's method used by the FragmentPagerAdapter when the fragment is added. Not sure whether this is a safe approach or not though.