Skip to content

Conversation

@huycozy
Copy link
Member

@huycozy huycozy commented Dec 19, 2025

Demo
before after
after.mov

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@huycozy huycozy self-assigned this Dec 19, 2025
@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Dec 19, 2025
Signed-off-by: huycozy <huy@nevercode.io>
Signed-off-by: huycozy <huy@nevercode.io>

Signed-off-by: huycozy <huy@nevercode.io>
@huycozy huycozy force-pushed the feat-Expose-the-Scroll-Controller-of-a-scrollable-TabBar branch from ffd0833 to b99463f Compare December 19, 2025 12:30
@huycozy huycozy marked this pull request as ready for review December 19, 2025 13:12
@huycozy huycozy requested a review from Piinks December 19, 2025 15:13
@@ -2016,6 +2054,13 @@ class _TabBarState extends State<TabBar> {
).add(widget.padding ?? EdgeInsets.zero)
: widget.padding;
_scrollController ??= _TabBarScrollController(this);
Copy link
Contributor

@navaronbracke navaronbracke Dec 19, 2025

Choose a reason for hiding this comment

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

Why don't we make _TabBarScrollController public but final?

I understand that the private _TabBarState needs to stay private, but why don't we include a private setter for it?

final class TabBarScrollController extends ScrollController {
  /// The state of the attached [TabBar], if any.
  ///
  /// Is null when not attached to a [TabBar].
  _TabBarState? _tabBarState;

  void _attachToTabBar(_TabBarState tabBar) {
     _tabBarState = tabBar;
  }

  @override
  ScrollPosition createScrollPosition(
    ScrollPhysics physics,
    ScrollContext context,
    ScrollPosition? oldPosition,
  ) {
    return _TabBarScrollPosition(
      physics: physics,
      context: context,
      oldPosition: oldPosition,
      tabBar: _tabBarState!,
    );
  }
}

Then we only need to call the attach method using a private helper method? (which handles creating an internal controller and attaches the this)

For example:

TabBarScrollController _setUpTabBarController() {
   final tabBarController = widget.tabBarController ?? TabBarScrollController();
   
   tabBarController._attachToTabBar(this);
   
   return tabBarController;
}

// ...
_scrollController ??= _setUpTabBarController()

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for your review, @navaronbracke!
If we proceed with this approach:

  1. how do we initialize a TabBarState to pass into the TabBarScrollController constructor? And we need to make TabBarState public too, for this purpose.
  2. Even if this were feasible, I'm quite concerned about the integrity and encapsulation of TabBarState class since it's marked as a public class.
  3. Since marking TabBarState as public, the cost of maintenance for it (new public API) may increase, as users may encounter unexpected issues with this change in their usage?

Also, looking back the original use case from OP at #123965 reported:"Exposing the Scroll Controller of a TabBar with isScrollable: true, will let developers apply custom behavior based on the scroll.", it means they only need offset from scrollController which is sufficient enough?

With these reasons, I think the callback approach is sufficient and safer.

Copy link
Contributor

@navaronbracke navaronbracke Dec 29, 2025

Choose a reason for hiding this comment

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

  1. It would never be a constructor parameter in my proposal.

For attaching a TabBarState, I had proposed using a new function, that is private to TabBarController only, so it handles this automatically.

  void _attachToTabBar(_TabBarState tabBar) {
     _tabBarState = tabBar;
  }

which is called right after the line that currently does this:

_scrollController ??= _TabBarScrollController(this);

It would then become:

_scrollController ??= _effectiveTabBarScrollController.._attach(this);

  1. TabBarState stays private. We should not change this.
  2. The only usage change is making TabBarScrollController's constructor public. Attaching to the private TabBarState is always going to be private API. Users will be able to use the public ScrollController API though (since the tab controller inherits it), which fixes this bug.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for your update!

It would never be a constructor parameter in my proposal.

I thought it'd be a constructor param as I saw your snippet code above, which contains widget.tabBarController:

final tabBarController = widget.tabBarController ?? TabBarScrollController();

Users will be able to use the public ScrollController API though (since the tab controller inherits it), which fixes this bug.

Could you please explain how users can access the exposed controller for their needs?

Also, would you mind submitting another pull request for your solution (which will include tests and sample codes too)? Then the team can consider which approach is better for this case. I think @Piinks is the best person to review this, just let's wait until the holidays are over.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, widget.tabBarController would be a new constructor parameter for the TabBar widget (a nullable TabBarController), not a change to the TabBarController constructor.

I will make a new pull request with my proposal. I'll add you as reviewer as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Piinks
Copy link
Contributor

Piinks commented Jan 7, 2026

Since there are 2 proposals here, I recommend we centralize discussion here: #123965 (comment) 💙

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

3 participants