3

I have a ListFragment that uses a header view. Both the header's contents and the list's are fetched from a background task. In order to not re-fetch the data on configuration changes, I am calling setRetainInstance and keeping the data on the fragment.

When the the configuration changes, the view is recreated, so it removes the header view that I previously populated. Since now I already have the data, I should just re-add the header view to the list.

Unfortunately when I try doing this... boom!

java.lang.IllegalStateException: Cannot add header view to list -- setAdapter 
has already been called.

Apparently, even tho the view is destroyed and onCreateView is called again, the list's adapter is already set (or the state is retained), making it impossible to add the header view again.

How can I keep the ListView's header or redraw it without recreating the fragment on orientation changes?

Draiken
  • 3,805
  • 2
  • 30
  • 48

3 Answers3

1

This is intended behaviour, take a look at the Android source code here for guidance on API 17, but really any will do. The relevant part is:

Add a fixed view to appear at the top of the list. If addHeaderView is called more than once, the views will appear in the order they were added. Views added using this call can take focus if they want. NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied cursor with one that will also account for header and footer views.

public void addHeaderView(View v, Object data, boolean isSelectable) {

    if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
        throw new IllegalStateException(
            "Cannot add header view to list -- setAdapter has already been" +
            "called.");  // Edit: SK9 wrapped this.
    }

    FixedViewInfo info = new FixedViewInfo();
    info.view = v;
    info.data = data;
    info.isSelectable = isSelectable;
    mHeaderViewInfos.add(info);

    // in the case of re-adding a header view, or adding one later on,
    // we need to notify the observer
    if (mAdapter != null && mDataSetObserver != null) {
        mDataSetObserver.onChanged();
    }
}

The adapter is not null when you come to add the header again and an exception is being raised. To resolve your issue, something along the following lines will do just fine:

setListAdapter(null);
getListView().addHeaderView(mHeader);
setListAdapter(new MyAdapter(getActivity(), items));

I wouldn't even classify this as a workaround. I encountered the same problem and this worked for me.

Apparently footers are treated very differently, see here:

public void addFooterView(View v, Object data, boolean isSelectable) {

    // NOTE: do not enforce the adapter being null here, since unlike in
    // addHeaderView, it was never enforced here, and so existing apps are
    // relying on being able to add a footer and then calling setAdapter to
    // force creation of the HeaderViewListAdapter wrapper

    FixedViewInfo info = new FixedViewInfo();
    info.view = v;
    info.data = data;
    info.isSelectable = isSelectable;
    mFooterViewInfos.add(info);

    // in the case of re-adding a footer view, or adding one later on,
    // we need to notify the observer
    if (mAdapter != null && mDataSetObserver != null) {
        mDataSetObserver.onChanged();
    }
}
Ken
  • 30,811
  • 34
  • 116
  • 155
  • The fix you suggested will actaully cause crashes on older versions like 2.3 – Heinrisch Sep 23 '13 at 17:24
  • "something along the following lines will do just fine" is an indication of how to proceed. The rest is up to you. – Ken Sep 25 '13 at 01:40
0

it's a know issue, but you can resolve it like this:

add header before the set adapter and remove him

Tomer Mor
  • 7,998
  • 5
  • 29
  • 43
  • 1
    I don't get it, I have to nullify the adapter to re-add the headerview? This will make the adapter get remote data again, won't it? – Draiken Jan 09 '13 at 16:10
  • Draiken is right. Junt call setListAdapter(null). That will solve the issue. – Bateramos May 24 '13 at 19:33
0

Yes, it's a known issue, but can be avoided with the proper approach. It seems that a solution similar to your problem exists.
These guys found a workaround: setSelected in OnItemClick in ListView
Hope it helps ;)

Community
  • 1
  • 1
bazyle
  • 756
  • 6
  • 13