airbnb/epoxy · error · IllegalStateException
The layout provided to EpoxyModelGroup must be a ViewGroup
Error message
The layout provided to EpoxyModelGroup must be a ViewGroup
What it means
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.
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
Example fix
// before
<!-- group_layout.xml -->
<ImageView .../>
// after
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView .../>
</FrameLayout> Defensive patterns
Strategy: validation
Validate before calling
val root = LayoutInflater.from(context).inflate(R.layout.group_layout, null)
check(root is ViewGroup) { "group layout root must be a ViewGroup" } Type guard
fun isValidGroupLayout(v: View) = v is ViewGroup
Try / catch
try { groupHolder.bindView(view) } catch (e: IllegalStateException) { Log.e(TAG, "group layout root must be a ViewGroup") } Prevention
- 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
When it happens
Trigger: An EpoxyModelGroup whose layout resource's root element is a View (e.g., ImageView, TextView) instead of a ViewGroup container (FrameLayout, LinearLayout, etc.).
Common situations: Setting a single-view layout as the group layout; a merge tag or wrong layout passed to EpoxyModelGroup's layoutRes.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- No view stubs found. If viewgroup is not empty it must…
- Insufficient view stubs for EpoxyModelGroup. $modelCount…
- Must have stable ids when saving view holder state
- State cannot be restored once views have been bound. It…
- Tried to restore instance state, but onSaveInstanceState…
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/3b67e2aaf357f9f5.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt:36
/**
* Get the root view group (aka
* [androidx.recyclerview.widget.RecyclerView.ViewHolder.itemView].
* You can override [EpoxyModelGroup.bind] and use this method to make custom
* changes to the root view.
*/
lateinit var rootView: ViewGroup
private set
private lateinit var childContainer: ViewGroup
private lateinit var stubs: List<ViewStubData>
private var boundGroup: EpoxyModelGroup? = null
private fun usingStubs(): Boolean = stubs.isNotEmpty()
override fun bindView(itemView: View) {
if (itemView !is ViewGroup) {
throw IllegalStateException(
"The layout provided to EpoxyModelGroup must be a ViewGroup"
)
}
rootView = itemView
childContainer = findChildContainer(rootView)
stubs = if (childContainer.childCount != 0) {
createViewStubData(childContainer)
} else {
emptyList()
}
}
/**
* By default the outermost viewgroup is used as the container that views are added to. However,
* users can specify a different, nested view group to use as the child container by marking it
* with a special id.View on GitHub (pinned to e45bd3a61f)