How to make my tabbar synced with scroll when touched certain widget without package? - flutter

I have initialised my tab.
tabs = [
Tab(text: tr("detail")),
Tab(text: tr("store")),
Tab(text: tr("review")),
];
_tabController = TabController(
length: tabs.length,
vsync: this,
);
Then i have my widget wrap inside CustomScrollView
CustomScrollView(
controller: _scrollController,
slivers: [
SliverToBoxAdapter(child: Consumer<MyProvider>(
builder: (context, myProvider, child) {
return Column(
children: [
Widget1(),
Widget2(),
Widget3(),
Widget4(),
],
);
}))
]
I want to assign the tab index to certain widget, when scroll until the widget then my tab will switch too. I will need to manually assign an unique index to each widget and bind to my tab. When scrolling allow to scroll to widget not visible on screen also. How should i actually start to achieve that? thanks

Doing this without packages - simply copy the code you need from the package if you don't want dependency.
From what i understood you have 3 problems to solve
When scrolled to the bottom of page, switch the tab
There already is a great answer for the problem
Answer: https://stackoverflow.com/a/54539182/15106600
Do something when certain widget is displayed
There is the package for it to detect if widget is displayed ( remember it triggers when it starts being visible not the full height displayed)
Answer: https://pub.dev/packages/visibility_detector
Navigate to next tab
NavigatorController.animateTo()
Answer: https://api.flutter.dev/flutter/material/TabController-class.html

Related

Scroll position lost if tab changed while scrolling animation is still ongoing

I have 3 views which are accessible via the bottom navigation tab. Each view has its own ListView, which looks like this:
// primary = bottomTabNavigation.index //
ListView(
controller: primary ? null : scrollController,
key: const PageStorageKey<String>('view1'),
primary: primary,
physics: primary
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
children: const [
Text("A"),
SizedBox(height: 1000),
Text("B"),
],
),
If I start a big swipe on view1, and switch to view2 via bottom tab navigator, the scroll position when I come back to view1 is still at the top. Somehow, the scroll position only saves upon the scrolling animation completing.
Is there some way to switch tabs and store the last position (without waiting for animation)?
Create a Key outside the build method
final _key = GlobalKey();
Step 1: make your widgets staefulWidget.
Step 2: now use AutomaticKeepAliveClientMixin using with keyword.
class _DealListState extends
State<DealList> with
AutomaticKeepAliveClientMixin<DealList>
{
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
// your current widget build
}}
It will keep your listview and other states when you moves from one page to another.
Note: if it's impossible to change every page to stateful widget then just make a new StatefulWidget that use AutomaticKeepAliveClientMixin and will take a child widget from outside and now you can use this widget to wrap your already present widget and can be used through the app.

How to navigate to another tab from TabBarView page flutter

I have a page HomePageScreen() and it has a TabBarView with four different pages.
TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: _tabController,
children: <Widget>[
PageOneScreen(),
PageTwoScreen(),
PageThreeScreen(context),
PageFourScreen(),
],
),
By default, the PageOneScreen() page shows in the HomePageScreen() and I can navigate to the other tabs quite fine.
I am trying to Navigator.popUntil(context, (route) => route.isFirst) from inside a different route and when it gets to PageOneScreen(), I want to navigate to tab 2 if a certain value in savedPrefrence = 2.
I have tried DefaultTabController.of(context).animateTo(1) on PageOneScreen() but it throws an error The method 'animateTo' was called on null. and I think it's because I don't have any tabcontroller reference in PageOneScreen().
How can I deal with this?
As you have defined a TabController, use it do switch between tabs like this:
_tabController.index = 2;
Considering that you want to navigate from inside PageOneScreen(), instantiate it with the controller, so it has access to it:
PageOneScreen(_tabController)

Why does `const` modifier avoid StreamBuilder rebuild in Flutter?

In my app's homepage I call a ScoreListPageBody that is rebuild when I change the Locale from another page (SettingsPage).
To force rebuild it, it contains a StreamBuilder that is triggered when the Locale changes and the users goes back to the HomePage (using Navigator.push(....).then(() => triggerStream())).
It works very well when I do this in my HomePage :
return Scaffold(
appBar: MenuAppBar(),
body: PageView(
controller: this._pageController,
children: [
ScoreListPageBody(),
FavoritesListPageBody(),
],
)
);
But this prevents the rebuild of ScoreListPageBody's StreamBuilder :
return Scaffold(
appBar: MenuAppBar(),
body: PageView(
controller: this._pageController,
children: const [
ScoreListPageBody(),
FavoritesListPageBody(),
],
)
);
I do not understand why, as const modifier should not impact the StreamBuilder's behavior, right ?
So, it works without const, but is it an issue to report to Flutter team ?
The const modifier impact the StreamBuilder because this is inside ScoreListPageBody() and you put the modifier in the PageView which is one step above the tree.
- PageView
-- ScoreList()
-- ...

How to dynamically disable vertical swipe in PageView

I would like to find a way to update the physics param to disable the swipe action in a parent PageView from a child widget.
I am using riverpod for updating the state in a child widget when it builds to know when I should pass NeverScrollableScrollPhysics to the physics param in a parent widget. But the thing is that this approach is causing my child widget to rebuild recursively, because this is making the PageView rebuild to update the physics param. So I really don't know what to do here.
I have this parent Widget that builds the PageView:
Widget build(BuildContext context) {
final _navBar = useProvider(bottomNavigationBarProvider);
return PageView(
physics: navBar.isSwipeBlocked ? const NeverScrollableScrollPhysics() : null,
controller: pageController,
onPageChanged: onPageChanged,
children: [
Beamer(
key: const Key('feed-tab'),
routerDelegate: BeamerDelegate(locationBuilder: (state) => FeedLocation(state)),
),
]
)
}
And the child Widget that updates the state variable:
Widget build(BuildContext context) {
final _navBar = useProvider(bottomNavigationBarProvider.notifier);
useEffect(() {
Future.microtask(() => _navBar.blockSwipe());
}, []);
return Container(...);
}
So when FeedLocation loads, it updates _navBar in an attempt for disabling the scroll behavior. But as I mentioned, this causes the parent to rebuild and FeedLocation to build again and then the recursive state..
The idea was to be able to go to FeedLocation, disable the scroll, then when go back, enable it again, but I don't see a solution for that.
I think I did already what this guy suggested https://github.com/flutter/flutter/issues/37510#issuecomment-738051469 using Riverpod
And I guess I am a similar situation as this guys from the same thread https://github.com/flutter/flutter/issues/37510#issuecomment-864416592
Is anybody able to see a solution or what I am doing wrong?
You should replace null as the secondary ScrollPhysics with PageScrollPhysics() and make sure to add setState(() {}); when you update the isSwipeBlocked variable.

Killing flutter gesture

I am trying to stop a gesture from continuing if a certain criteria is met.
The scenario is the user is swiping a tab bar and if they go to the next tab and the condition is true it should disable further swiping. I have tried to put the tab bar in a stack with an Absorb pointer container on top but if they don't let go of the original gesture (i.e. they got to the new tab but didnt let go of the screen) it still allows them to drag through it.
is there anyway to stop the original swipe gesture?
i found this cancel method but i have no idea how to access it
https://api.flutter.dev/flutter/gestures/Drag/cancel.html
In your case there is no need to handle gesture out of the TabBarView simply change the ScrollPhysics.
Here a code example:
final isDisable = ValueNotifier(false);
tabController.animation?.addListener(() {
//The scrolling will be disabled if the second tab is displayed so you can
//add your own logic, may be just checking tabController.index
if (tabController.animation!.value == 1.0) {
isDisable.value = true;
}
});
return Scaffold(
body: ValueListenableBuilder(
valueListenable: isDisable,
builder: (context, bool value, child) => TabBarView(
controller: tabController,
// changing the physics to NeverScrollableScrollPhysics will disable scrolling
physics: value
? NeverScrollableScrollPhysics()
: AlwaysScrollableScrollPhysics(),
children: children,
),
),
);