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

  1. Wrap the group's layout in a ViewGroup root such as FrameLayout
  2. Verify the layout id passed to EpoxyModelGroup points to a layout with a ViewGroup root
  3. 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

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


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)