{"record":{"id":"3b67e2aaf357f9f5","repo":"airbnb/epoxy","slug":"the-layout-provided-to-epoxymodelgroup-must-be-a-viewgroup","errorCode":null,"errorMessage":"The layout provided to EpoxyModelGroup must be a ViewGroup","messagePattern":"The layout provided to EpoxyModelGroup must be a ViewGroup","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt","lineNumber":36,"sourceCode":"\n    /**\n     * Get the root view group (aka\n     * [androidx.recyclerview.widget.RecyclerView.ViewHolder.itemView].\n     * You can override [EpoxyModelGroup.bind] and use this method to make custom\n     * changes to the root view.\n     */\n    lateinit var rootView: ViewGroup\n        private set\n\n    private lateinit var childContainer: ViewGroup\n    private lateinit var stubs: List<ViewStubData>\n    private var boundGroup: EpoxyModelGroup? = null\n\n    private fun usingStubs(): Boolean = stubs.isNotEmpty()\n\n    override fun bindView(itemView: View) {\n        if (itemView !is ViewGroup) {\n            throw IllegalStateException(\n                \"The layout provided to EpoxyModelGroup must be a ViewGroup\"\n            )\n        }\n\n        rootView = itemView\n        childContainer = findChildContainer(rootView)\n\n        stubs = if (childContainer.childCount != 0) {\n            createViewStubData(childContainer)\n        } else {\n            emptyList()\n        }\n    }\n\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.","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt#L18-L54","documentation":"ModelGroupHolder binds the root view of an EpoxyModelGroup and needs a ViewGroup to hold child model views. If the layout root bound is a plain View (not a ViewGroup), it throws this IllegalStateException since children cannot be attached.","triggerScenarios":"An EpoxyModelGroup whose layout resource's root element is a View (e.g., ImageView, TextView) instead of a ViewGroup container (FrameLayout, LinearLayout, etc.).","commonSituations":"Setting a single-view layout as the group layout; a merge tag or wrong layout passed to EpoxyModelGroup's layoutRes.","solutions":["Wrap the group's layout in a ViewGroup root such as FrameLayout","Verify the layout id passed to EpoxyModelGroup points to a layout with a ViewGroup root","If a single child view suffices, use that model directly instead of EpoxyModelGroup"],"exampleFix":"// before\n<!-- group_layout.xml -->\n<ImageView .../>\n// after\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n  <ImageView .../>\n</FrameLayout>","handlingStrategy":"validation","validationCode":"val root = LayoutInflater.from(context).inflate(R.layout.group_layout, null)\ncheck(root is ViewGroup) { \"group layout root must be a ViewGroup\" }","typeGuard":"fun isValidGroupLayout(v: View) = v is ViewGroup","tryCatchPattern":"try { groupHolder.bindView(view) } catch (e: IllegalStateException) { Log.e(TAG, \"group layout root must be a ViewGroup\") }","preventionTips":["Always wrap EpoxyModelGroup layouts in FrameLayout/LinearLayout","Inflate-and-assert the layout root in unit tests","Never use single-view or <merge> roots for group layouts"],"tags":["android","epoxy","layout","modelgroup"],"backgroundTag":"type-mismatch","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"}