Skip to content

Add useIndexedTab property for smooth tab switching with lazy-loaded indexed building#2190

Merged
usmanvrtx merged 8 commits intomainfrom
indexed_tabbar
May 4, 2026
Merged

Add useIndexedTab property for smooth tab switching with lazy-loaded indexed building#2190
usmanvrtx merged 8 commits intomainfrom
indexed_tabbar

Conversation

@usmanvrtx
Copy link
Copy Markdown
Contributor

Description:
Introduces useIndexedTab, a boolean property that enables IndexedStack-based tab building in TabBar widgets.

Problem it solves:
By default, when switching tabs, the previous tab's widget is removed from the widget tree. Switching back to that tab requires rebuilding it, which causes lag or freezing if the tab contains heavy data.

Solution:
With useIndexedTab: true, tabs are built on-demand and cached. When you switch to a tab for the first time, it's built and cached. Tab switching becomes instant—no rebuild, no delay, no freeze. Unvisited tabs remain lazy and are not built until accessed.

How it works:

  • Tabs are built only when first accessed (lazy loading)
  • Built tabs are cached and kept in the widget tree while the screen is active
  • Uses IndexedStack (expanded) or Column with Offstage (non-expanded) to maintain widgets in tree
  • Only the active tab is visible; others remain cached and ready

Impact:
Eliminates freezed transitions when switching between tabs with heavy content.

Screen.Recording.2026-04-28.at.01.10.53.mov

@usmanvrtx usmanvrtx requested a review from TheNoumanDev April 27, 2026 20:12
@usmanvrtx usmanvrtx self-assigned this Apr 27, 2026
Copilot AI review requested due to automatic review settings April 27, 2026 20:12
@usmanvrtx usmanvrtx added the enhancement New feature or request label Apr 27, 2026
@usmanvrtx usmanvrtx review requested due to automatic review settings April 27, 2026 20:12
…Controller

Co-authored-by: Copilot <copilot@github.com>
Copilot AI review requested due to automatic review settings April 29, 2026 22:26
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in useIndexedTab flag to TabBar widgets to keep visited tab bodies alive (built lazily and cached) to avoid lag/freezes when switching back to heavy tabs.

Changes:

  • Added useIndexedTab property plumbing on BaseTabBar / TabBarController.
  • Implemented lazy, cached tab-body rendering via IndexedStack (expanded) or Column + Offstage (non-expanded).
  • Added a selectedIndex bounds fix during TabController reinitialization for conditional tab visibility.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
modules/ensemble/lib/layout/tab_bar.dart Adds useIndexedTab setter, tab-body caching, and controller reinit bounds guard.
modules/ensemble/lib/layout/tab/tab_bar_controller.dart Adds useIndexedTab field to the controller (plus minor formatting change).
Comments suppressed due to low confidence (1)

modules/ensemble/lib/layout/tab_bar.dart:127

  • _getValidInitialIndex() clamps the index used for TabController.initialIndex, but it does not update widget._controller.selectedIndex itself. If selectedIndex is configured (or data-bound) to a value >= items.length, the controller still holds the invalid value and buildSelectedTab() / _buildTabBodies() will index into items[selectedIndex] and crash. Consider clamping and writing back selectedIndex to the computed safe value when initializing/reinitializing the controller.
  void _initializeTabController() {
    final safeIndex = _getValidInitialIndex();
    if (widget.controller.useIndexedTab) {
      _cache = List<Widget?>.filled(widget.controller.items.length, null);
    }
    tabController = TabController(
      initialIndex: safeIndex,
      length: widget.controller.items.length,
      vsync: this,
    );
    tabController.addListener(notifyListener);
  }

  int _getValidInitialIndex() {
    // ensure the selectedIndex is valid, otherwise reset
    return widget._controller.selectedIndex < widget.controller.items.length
        ? widget._controller.selectedIndex
        : 0;
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 99 to +113
class TabBarState extends BaseTabBarState {
// Cache for indexed tab building mode
late List<Widget?> _cache;

@override
void initState() {
super.initState();
_initializeTabController();
}

void _initializeTabController() {
final safeIndex = _getValidInitialIndex();
if (widget.controller.useIndexedTab) {
_cache = List<Widget?>.filled(widget.controller.items.length, null);
}
Copy link
Copy Markdown
Member

@TheNoumanDev TheNoumanDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Muhammad Usman and others added 3 commits May 4, 2026 16:48
@usmanvrtx usmanvrtx requested a review from TheNoumanDev May 4, 2026 13:12
Copy link
Copy Markdown
Member

@TheNoumanDev TheNoumanDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@usmanvrtx usmanvrtx merged commit 567a0a2 into main May 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants