ksoichiro/Android-ObservableScrollView · error · IllegalStateException

Cannot add header view to grid -- setAdapter has already…

Error message

Cannot add header view to grid -- setAdapter has already been called.

What it means

ObservableGridView.addFooterView throws this IllegalStateException because Android's GridView/AdapterView contract requires header/footer views to be installed before a data adapter is set. Internally, if getAdapter() is non-null and is not the library's HeaderViewGridAdapter wrapper, the grid has already had setAdapter() called with a plain adapter, and adding a footer afterwards cannot be done safely. The message says 'header view' because the implementation reuses the header-error message for the footer path.

Solutions

  1. Call addFooterView(v, data, isSelectable) BEFORE setAdapter() on the ObservableGridView.
  2. If the adapter was already set, call setAdapter(null) (or setAdapter with a fresh HeaderViewGridAdapter-wrapped adapter) before addFooterView, then re-set the adapter through the grid's setAdapter, which wraps it in HeaderViewGridAdapter.
  3. Restructure so footer views are added once at initialization time, before any adapter assignment.
  4. If dynamic content is needed in the footer, keep a single footer view added early and toggle its visibility/content instead of adding/removing footers later.

Example fix

// before
grid.setAdapter(adapter);
grid.addFooterView(footerView); // throws IllegalStateException

// after
grid.addFooterView(footerView);
grid.setAdapter(adapter);
Defensive patterns

Strategy: validation

Validate before calling

if (grid.getAdapter() == null || grid.getAdapter() instanceof HeaderViewGridAdapter) {
    grid.addFooterView(footerView);
    grid.setAdapter(adapter); // if not already wrapped
} else {
    grid.setAdapter(null);
    grid.addFooterView(footerView);
    grid.setAdapter(adapter);
}

Type guard

boolean canAddGridViewFixedView(ObservableGridView grid) {
    ListAdapter a = grid.getAdapter();
    return a == null || a instanceof HeaderViewGridAdapter;
}

Try / catch

try {
    grid.addFooterView(footerView);
} catch (IllegalStateException e) {
    // adapter already set: reset and retry in correct order
    grid.setAdapter(null);
    grid.addFooterView(footerView);
    grid.setAdapter(adapter);
}

Prevention

When it happens

Trigger: Calling observableGridView.addFooterView(v) (or the two/three-arg overloads) after setAdapter(adapter) has been called with a regular ListAdapter; calling addFooterView twice with a normal adapter in between; re-adding a footer after the adapter was reset via setAdapter(null) then setAdapter(realAdapter).

Common situations: Setting the grid adapter in onCreate/onBindView then later adding a footer when loading more data; porting ListView addFooterView-then-setAdapter ordering code without reversing it for this grid; RecyclerView-style habits where adapters can be swapped freely; fragment view recreation where setAdapter runs before footer setup.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.


AI-assisted analysis of ksoichiro/Android-ObservableScrollView@47a5fb2db5 (2026-09-10). Data as JSON: /api/errors/724b16647f50f712. Report an issue: GitHub.

Appendix: source

Thrown at library/src/main/java/com/github/ksoichiro/android/observablescrollview/ObservableGridView.java:229

                    }
                    // Even when this can't be scrolled anymore,
                    // simply returning false here may cause subView's click,
                    // so delegate it to super.
                    return super.onTouchEvent(ev);
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void addFooterView(View v) {
        addFooterView(v, null, true);
    }

    public void addFooterView(View v, Object data, boolean isSelectable) {
        ListAdapter mAdapter = getAdapter();
        if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) {
            throw new IllegalStateException(
                "Cannot add header view to grid -- setAdapter has already been called.");
        }

        ViewGroup.LayoutParams lyp = v.getLayoutParams();

        FixedViewInfo info = new FixedViewInfo();
        FrameLayout fl = new FullWidthFixedViewLayout(getContext());

        if (lyp != null) {
            v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));
            fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));
        }
        fl.addView(v);
        info.view = v;
        info.viewContainer = fl;
        info.data = data;
        info.isSelectable = isSelectable;
        mFooterViewInfos.add(info);

View on GitHub (pinned to 47a5fb2db5)