{"record":{"id":"a30181f7987597aa","repo":"airbnb/epoxy","slug":"no-view-stubs-found-if-viewgroup-is-not-empty-it-must","errorCode":null,"errorMessage":"No view stubs found. If viewgroup is not empty it must contain ViewStubs.","messagePattern":"No view stubs found\\. If viewgroup is not empty it must contain ViewStubs\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt","lineNumber":68,"sourceCode":"\n    /**\n     * By default the outermost viewgroup is used as the container that views are added to. However,\n     * users can specify a different, nested view group to use as the child container by marking it\n     * with a special id.\n     */\n    private fun findChildContainer(outermostRoot: ViewGroup): ViewGroup {\n        val customRoot = outermostRoot.findViewById<View>(R.id.epoxy_model_group_child_container)\n\n        return customRoot as? ViewGroup ?: outermostRoot\n    }\n\n    private fun createViewStubData(viewGroup: ViewGroup): List<ViewStubData> {\n        return ArrayList<ViewStubData>(4).apply {\n\n            collectViewStubs(viewGroup, this)\n\n            if (isEmpty()) {\n                throw IllegalStateException(\n                    \"No view stubs found. If viewgroup is not empty it must contain ViewStubs.\"\n                )\n            }\n        }\n    }\n\n    private fun collectViewStubs(\n        viewGroup: ViewGroup,\n        stubs: ArrayList<ViewStubData>\n    ) {\n        for (i in 0 until viewGroup.childCount) {\n            val child = viewGroup.getChildAt(i)\n\n            if (child is ViewGroup) {\n                collectViewStubs(child, stubs)\n            } else if (child is ViewStub) {\n                stubs.add(ViewStubData(viewGroup, child, i))\n            }","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt#L50-L86","documentation":"ModelGroupHolder binds an EpoxyModelGroup by inflating a layout that must contain ViewStub entries, one per model. createViewStubData recursively collects those ViewStubs from the view group; if none are found, the ViewGroup passed to bind was empty of ViewStubs, so binding cannot proceed and the library throws this IllegalStateException. It exists to catch layout misuse early rather than fail later with confusing NPEs.","triggerScenarios":"Calling bind (or bindGroupIfNeeded indirectly) with a ViewGroup whose layout contains no ViewStub children at all — e.g. an empty container or a layout built from real views instead of ViewStubs.","commonSituations":"Passing a regular layout (LinearLayout/FrameLayout with normal views) where an EpoxyModelGroup stub layout is expected; accidentally inflating the wrong resource id; layouts refactored so ViewStubs were replaced with plain views; using a ViewGroup that is already populated with children.","solutions":["Use a layout resource whose direct/descendant children are ViewStub elements, one per model in the group.","Verify the resource id passed when creating/inflating the group layout is the intended stub layout, not a regular item layout.","Ensure the ViewGroup passed to bind is empty of other content and dedicated to the EpoxyModelGroup.","If you migrated from regular views, convert each child view back to a <ViewStub> with the real layout in its layoutResource attribute."],"exampleFix":"// before: res/layout/group_item.xml with real views\n<LinearLayout ...>\n  <TextView .../>\n  <ImageView .../>\n</LinearLayout>\n\n// after: one ViewStub per model\n<LinearLayout ...>\n  <ViewStub android:id=\"@+id/stub1\" android:layout=\"@layout/model1\" .../>\n  <ViewStub android:id=\"@+id/stub2\" android:layout=\"@layout/model2\" .../>\n</LinearLayout>","handlingStrategy":"validation","validationCode":"fun ViewGroup.validateHasViewStubs() {\n  check(collectStubs(this).isNotEmpty()) {\n    \"Layout $id used for EpoxyModelGroup contains no ViewStubs\"\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep one dedicated stub-only layout per EpoxyModelGroup and never mix plain views in.","Assert stub count in a debug-only unit test that inflates the group layout.","Never add non-stub children to the ViewGroup handed to bind."],"tags":["android","epoxy","viewstub","layout","illegal-state"],"backgroundTag":"missing-required-config-field","analyzedSha":"e45bd3a61fe3a1f130e184f5b8dcf172ab99025a","analyzedAt":"2026-09-13T03:24:16.052Z","contentChangedAt":"2026-09-13T03:24:16.052Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}