{"record":{"id":"418e7106ecfdd3b5","repo":"material-components/material-components-android","slug":"tablayoutmediator-is-already-attached","errorCode":null,"errorMessage":"TabLayoutMediator is already attached","messagePattern":"TabLayoutMediator is already attached","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"lib/java/com/google/android/material/tabs/TabLayoutMediator.java","lineNumber":113,"sourceCode":"      @NonNull TabConfigurationStrategy tabConfigurationStrategy) {\n    this.tabLayout = tabLayout;\n    this.viewPager = viewPager;\n    this.autoRefresh = autoRefresh;\n    this.smoothScroll = smoothScroll;\n    this.tabConfigurationStrategy = tabConfigurationStrategy;\n  }\n\n  /**\n   * Link the TabLayout and the ViewPager2 together. Must be called after ViewPager2 has an adapter\n   * set. To be called on a new instance of TabLayoutMediator or if the ViewPager2's adapter\n   * changes.\n   *\n   * @throws IllegalStateException If the mediator is already attached, or the ViewPager2 has no\n   *     adapter.\n   */\n  public void attach() {\n    if (attached) {\n      throw new IllegalStateException(\"TabLayoutMediator is already attached\");\n    }\n    adapter = viewPager.getAdapter();\n    if (adapter == null) {\n      throw new IllegalStateException(\n          \"TabLayoutMediator attached before ViewPager2 has an \" + \"adapter\");\n    }\n    attached = true;\n\n    // Add our custom OnPageChangeCallback to the ViewPager\n    onPageChangeCallback = new TabLayoutOnPageChangeCallback(tabLayout);\n    viewPager.registerOnPageChangeCallback(onPageChangeCallback);\n\n    // Now we'll add a tab selected listener to set ViewPager's current item\n    onTabSelectedListener = new ViewPagerOnTabSelectedListener(viewPager, smoothScroll);\n    tabLayout.addOnTabSelectedListener(onTabSelectedListener);\n\n    // Now we'll populate ourselves from the pager adapter, adding an observer if\n    // autoRefresh is enabled","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/material-components/material-components-android/blob/ac7e18efeefb331850c561faf9ab8bf81d27ba68/lib/java/com/google/android/material/tabs/TabLayoutMediator.java#L95-L131","documentation":"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.","triggerScenarios":"Calling mediator.attach() twice; reusing one TabLayoutMediator field across fragment view recreations without creating a new mediator or calling detach().","commonSituations":"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.","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."],"exampleFix":"// before\nmediator.attach(); // second call after re-create -> throws\n\n// after (onViewCreated)\nif (mediator != null) mediator.detach();\nmediator = new TabLayoutMediator(tabLayout, viewPager2, configurator);\nmediator.attach();","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  mediator.attach();\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"already attached\")) {\n    // harmless duplicate attach — ignore or detach+reattach\n  } else throw e;\n}","preventionTips":["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."],"tags":["tablayout","viewpager2","lifecycle","android"],"backgroundTag":null,"analyzedSha":"ac7e18efeefb331850c561faf9ab8bf81d27ba68","analyzedAt":"2026-08-14T15:35:35.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}