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
- Call addFooterView(v, data, isSelectable) BEFORE setAdapter() on the ObservableGridView.
- 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.
- Restructure so footer views are added once at initialization time, before any adapter assignment.
- 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
- Always add header/footer views immediately after constructing the ObservableGridView and before setAdapter().
- Check getAdapter() == null before calling addHeaderView/addFooterView in debug builds.
- Centralize grid setup in one method so ordering (headers/footers first, adapter last) is enforced.
- Never call setAdapter() twice with plain adapters on a grid that uses headers/footers.
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)