material-components/material-components-android · error · IllegalStateException
TabLayoutMediator is already attached
Error message
TabLayoutMediator is already attached
What it means
TabLayoutMediator.link a TabLayout with a ViewPager2 and its lifecycle is one-shot: attach() throws IllegalStateException if called when the attached flag is already true. Re-attaching an already-attached mediator would double-register page change callbacks and tab listeners.
Source
Thrown at lib/java/com/google/android/material/tabs/TabLayoutMediator.java:113
@NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this.tabLayout = tabLayout;
this.viewPager = viewPager;
this.autoRefresh = autoRefresh;
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 enabledView on GitHub (pinned to ac7e18efee)
Solutions
- Create a new TabLayoutMediator in each onViewCreated and attach() once there.
- If you must reuse the instance, call detach() before attach() (detach clears listeners and the attached flag).
- When only the adapter's data changed, keep the mediator attached and notify the adapter instead of re-attaching.
Example fix
// before mediator.attach(); // second call after re-create -> throws // after (onViewCreated) if (mediator != null) mediator.detach(); mediator = new TabLayoutMediator(tabLayout, viewPager2, configurator); mediator.attach();
Defensive patterns
Strategy: try-catch
Try / catch
try {
mediator.attach();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().contains("already attached")) {
// harmless duplicate attach — ignore or detach+reattach
} else throw e;
} Prevention
- Create a fresh TabLayoutMediator per onViewCreated and attach once.
- Track your own boolean attached flag if reuse is unavoidable, or call detach() before attach().
- Call mediator.detach() in onDestroyView to keep pairs balanced.
When it happens
Trigger: Calling mediator.attach() twice; reusing one TabLayoutMediator field across fragment view recreations without creating a new mediator or calling detach().
Common situations: Fragment onDestroyView detaches (or not) and onViewCreated re-attaches the same stale mediator; adapter refresh code that calls attach() again after data changes instead of notifyDataSetChanged.
Related errors
- TabLayoutMediator attached before ViewPager2 has an adapter
- dateSelector should not be null. Use MaterialTextInputPicker
- Sheet view reference is null; sheet edge cannot be changed i
- Sheet view has been laid out; sheet edge cannot be changed o
- Tab belongs to a different TabLayout.
AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14).
Data as JSON: /api/errors/418e7106ecfdd3b5.
Report an issue: GitHub.