material-components/material-components-android · error · IllegalStateException
TabLayoutMediator attached before ViewPager2 has an adapter
Error message
TabLayoutMediator attached before ViewPager2 has an adapter
What it means
TabLayoutMediator.attach() reads viewPager.getAdapter() and throws IllegalStateException when it is null, because the mediator's callbacks and tab population are driven entirely by adapter callbacks. The doc contract states attach() must run after ViewPager2 has an adapter set.
Source
Thrown at lib/java/com/google/android/material/tabs/TabLayoutMediator.java:117
this.smoothScroll = smoothScroll;
this.tabConfigurationStrategy = tabConfigurationStrategy;
}
/**
* Link the TabLayout and the ViewPager2 together. Must be called after ViewPager2 has an adapter
* set. To be called on a new instance of TabLayoutMediator or if the ViewPager2's adapter
* changes.
*
* @throws IllegalStateException If the mediator is already attached, or the ViewPager2 has no
* adapter.
*/
public void attach() {
if (attached) {
throw new IllegalStateException("TabLayoutMediator is already attached");
}
adapter = viewPager.getAdapter();
if (adapter == null) {
throw new IllegalStateException(
"TabLayoutMediator attached before ViewPager2 has an " + "adapter");
}
attached = true;
// Add our custom OnPageChangeCallback to the ViewPager
onPageChangeCallback = new TabLayoutOnPageChangeCallback(tabLayout);
viewPager.registerOnPageChangeCallback(onPageChangeCallback);
// Now we'll add a tab selected listener to set ViewPager's current item
onTabSelectedListener = new ViewPagerOnTabSelectedListener(viewPager, smoothScroll);
tabLayout.addOnTabSelectedListener(onTabSelectedListener);
// Now we'll populate ourselves from the pager adapter, adding an observer if
// autoRefresh is enabled
if (autoRefresh) {
// Register our observer on the new adapter
pagerAdapterObserver = new PagerAdapterObserver();
adapter.registerAdapterDataObserver(pagerAdapterObserver);View on GitHub (pinned to ac7e18efee)
Solutions
- Set the adapter first: viewPager2.setAdapter(adapter); then mediator.attach().
- If the adapter arrives asynchronously, create and attach the mediator only after the adapter is set (e.g. inside the observer that delivers the adapter).
- Alternatively use RecyclerView.Adapter state restoration carefully: keep mediator creation lazy until both pager and adapter are ready.
Example fix
// before new TabLayoutMediator(tabLayout, viewPager2, true, configurator).attach(); viewPager2.setAdapter(adapter); // attach ran too early // after viewPager2.setAdapter(adapter); new TabLayoutMediator(tabLayout, viewPager2, true, configurator).attach();
Defensive patterns
Strategy: validation
Validate before calling
if (viewPager2.getAdapter() != null) {
new TabLayoutMediator(tabLayout, viewPager2, configurator).attach();
} else {
// set adapter first, or defer attach until the adapter is delivered
} Prevention
- Always viewPager2.setAdapter(...) before mediator.attach().
- With async adapters, attach inside the callback that delivers the adapter.
- Treat attach() as the last step of pager setup.
When it happens
Trigger: Calling new TabLayoutMediator(tabLayout, viewPager2, ...).attach() before viewPager2.setAdapter(adapter).
Common situations: Adapter set asynchronously (loaded from a ViewModel LiveData whose observer fires after attach); initialization-order mistakes in onViewCreated; adapter assignment behind a data-load callback while attach ran eagerly.
Related errors
- Tab not attached to a TabLayout
- TabLayoutMediator is already attached
- Tab belongs to a different TabLayout.
- Tab does not belong to this TabLayout.
- is not a valid TabIndicatorAnimationMode
AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14).
Data as JSON: /api/errors/54358079100a9a0f.
Report an issue: GitHub.