{"record":{"id":"724b16647f50f712","repo":"ksoichiro/Android-ObservableScrollView","slug":"cannot-add-header-view-to-grid-setadapter-has-a","errorCode":null,"errorMessage":"Cannot add header view to grid -- setAdapter has already been called.","messagePattern":"Cannot add header view to grid -- setAdapter has already been called\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"library/src/main/java/com/github/ksoichiro/android/observablescrollview/ObservableGridView.java","lineNumber":229,"sourceCode":"                    }\n                    // Even when this can't be scrolled anymore,\n                    // simply returning false here may cause subView's click,\n                    // so delegate it to super.\n                    return super.onTouchEvent(ev);\n                }\n                break;\n        }\n        return super.onTouchEvent(ev);\n    }\n\n    public void addFooterView(View v) {\n        addFooterView(v, null, true);\n    }\n\n    public void addFooterView(View v, Object data, boolean isSelectable) {\n        ListAdapter mAdapter = getAdapter();\n        if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) {\n            throw new IllegalStateException(\n                \"Cannot add header view to grid -- setAdapter has already been called.\");\n        }\n\n        ViewGroup.LayoutParams lyp = v.getLayoutParams();\n\n        FixedViewInfo info = new FixedViewInfo();\n        FrameLayout fl = new FullWidthFixedViewLayout(getContext());\n\n        if (lyp != null) {\n            v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));\n            fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));\n        }\n        fl.addView(v);\n        info.view = v;\n        info.viewContainer = fl;\n        info.data = data;\n        info.isSelectable = isSelectable;\n        mFooterViewInfos.add(info);","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/ksoichiro/Android-ObservableScrollView/blob/47a5fb2db5e93d923a8c6772cde48bbb7d932345/library/src/main/java/com/github/ksoichiro/android/observablescrollview/ObservableGridView.java#L211-L247","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\ngrid.setAdapter(adapter);\ngrid.addFooterView(footerView); // throws IllegalStateException\n\n// after\ngrid.addFooterView(footerView);\ngrid.setAdapter(adapter);","handlingStrategy":"validation","validationCode":"if (grid.getAdapter() == null || grid.getAdapter() instanceof HeaderViewGridAdapter) {\n    grid.addFooterView(footerView);\n    grid.setAdapter(adapter); // if not already wrapped\n} else {\n    grid.setAdapter(null);\n    grid.addFooterView(footerView);\n    grid.setAdapter(adapter);\n}","typeGuard":"boolean canAddGridViewFixedView(ObservableGridView grid) {\n    ListAdapter a = grid.getAdapter();\n    return a == null || a instanceof HeaderViewGridAdapter;\n}","tryCatchPattern":"try {\n    grid.addFooterView(footerView);\n} catch (IllegalStateException e) {\n    // adapter already set: reset and retry in correct order\n    grid.setAdapter(null);\n    grid.addFooterView(footerView);\n    grid.setAdapter(adapter);\n}","preventionTips":["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."],"tags":["android","gridview","adapter","illegal-state","header-footer-view"],"backgroundTag":"invalid-state-transition","analyzedSha":"47a5fb2db5e93d923a8c6772cde48bbb7d932345","analyzedAt":"2026-09-10T14:31:26.218Z","contentChangedAt":"2026-09-10T14:31:26.218Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}