Change badge number - flutter

In MainPage, it has 2 bottom navigation bar. One is icon with text, another is icon with text and badge number. When my app is launched, the badge is display 3 in second tab. This works fine.
class MainPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MainPageState();
}
method(int num) => _MainPageState().showBadge(num);
}
class _MainPageState extends State<MainPage>
with SingleTickerProviderStateMixin {
int _selectedIndex = 0;
int count = 0;
TabController _tabController;
PageController _pageController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this);
_pageController = PageController(initialPage: _selectedIndex);
showBadge(3);
}
void showBadge(int number) {
setState(() {
count = number;
});
}
void onPageChange(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onItemTapped(int index) {
_pageController.animateToPage(index,
duration: kTabScrollDuration, curve: Curves.ease);
}
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
body: FixTabBarView(
pageController: _pageController,
onPageChange: onPageChange,
tabController: _tabController,
children: <Widget>[
TabA(),
TabB(),
]),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.A), title: Text('TabA')),
BottomNavigationBarItem(
icon: Stack(children: <Widget>[
Icon(
Icons.B,
),
Positioned(
top: 1.0,
right: 0.0,
child: Stack(
children: <Widget>[
Icon(Icons.brightness_1, size: 18, color: Colors.red),
Positioned(
top: 1.0,
right: 4.0,
child: new Text(count.toString(),
style: new TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.w500)),
)
],
),
)
]),
title: Text('TabB'),
),
],
currentIndex: _selectedIndex,
fixedColor: Colors.blue,
onTap: _onItemTapped,
),
),
onWillPop: () {},
);
}
}
When tab 2 is clicked, I want the badge change to 1,but it throws error.
class TabB extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TabBState();
}
class _TabBState extends State<TabB> {
#override
void initState() {
super.initState();
_bloc.callApi().then((onValue){
MainPage().method(onValue); // onValue is the number return from server
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Tab 2"),
));
}
}
Error
════════ Exception caught by widgets library
═══════════════════════════════════ The following assertion was thrown
building NotificationListener: setState()
called in constructor: _MainPageState#6ed53(lifecycle state: created,
no widget, not mounted)
This happens when you call setState() on a State object for a widget
that hasn't been inserted into the widget tree yet. It is not
necessary to call setState() in the constructor, since the state is
already assumed to be dirty when it is initially created.

Instead of trying to call a function from your parent widget to modify the badge when that child widget is loaded, you should add a listener to your _tabController and change the badge when the tab is selected, like this:
#override
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this);
_tabController.addListener((){
if(_tabController.index == 1){
setState(() {
showBadge(1);
});
}
});
_pageController = PageController(initialPage: _selectedIndex);
showBadge(3);
}
Make sure you adjust the if for the tab index you want to match.
On your TabB you can declare that it accepts a Function as part of its constructor and then call that function:
class TabB extends StatefulWidget {
final Function showBadge;
TabB({this.showBadge});
#override
State<StatefulWidget> createState() => _TabBState();
}
class _TabBState extends State<TabB> {
#override
void initState() {
super.initState();
_bloc.callApi().then((onValue){
widget.showBadge(onValue); // onValue is the number return from server
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Tab 2"),
)
);
}
}
On your Main widget:
FixTabBarView(
pageController: _pageController,
onPageChange: onPageChange,
tabController: _tabController,
children: <Widget>[
TabA(),
TabB(showBadge: showBadge,),
]
)

Related

Flutter TabBar and TabBarView get out of sync when dynamically adjusting number of tabs

I have a situation where I have one Widget which lets me select from a list which tab options should be displayed in another Widget (the 2nd Widget has a TabController).
I'm using a ChangeNotifier to keep the state of which tabs are selected to be in the list.
It all works very well except for the situation when I am on the last tab and then delete it - in which case it still works, but the TabBar goes back to the first tab, while the TabBarView goes back to the second tab.
I've tried a plethora of different approaches to fix this (adding keys to the widgets, manually saving the tab controller index in state and navigating there after a delay, adding callbacks in the top level widget that call a setState) none of which has any effect.
Here is the code in full - I've tried to make it the smallest possible version of what I'm doing:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Tab Refresh Issue Demo',
home: Scaffold(body:
ChangeNotifierProvider<CurrenLTabsProvider>(
create: (_) => CurrenLTabsProvider(),
child: Consumer<CurrenLTabsProvider>(
builder: (context, tp, child) =>
Row(
children: [
const SizedBox(
child: TabSelectionWidget(),
width: 200,
height: 1000,
),
SizedBox(
child: TabWidget(tp.availableTabItems, tp._selectedTabIds),
width: 800,
height: 1000,
),
],
),
),
),
),
);
}
}
class CurrenLTabsProvider extends ChangeNotifier {
List<MyTabItem> availableTabItems = [
MyTabItem(1, 'Tab 1', const Text('Content for Tab 1')),
MyTabItem(2, 'Tab 2', const Text('Content for Tab 2')),
MyTabItem(3, 'Tab 3', const Text('Content for Tab 3')),
// MyTabItem(4, 'Tab 4', const Text('Content for Tab 4')),
// MyTabItem(5, 'Tab 5', const Text('Content for Tab 5')),
];
List<int> _selectedTabIds = [];
int currentTabIndex = 0;
set selectedTabs(List<int> ids) {
_selectedTabIds = ids;
notifyListeners();
}
List<int> get selectedTabs => _selectedTabIds;
void doNotifyListeners() {
notifyListeners();
}
}
class MyTabItem {
final int id;
final String title;
final Widget widget;
MyTabItem(this.id, this.title, this.widget);
}
class TabSelectionWidget extends StatefulWidget {
const TabSelectionWidget({Key? key}) : super(key: key);
#override
_TabSelectionWidgetState createState() => _TabSelectionWidgetState();
}
class _TabSelectionWidgetState extends State<TabSelectionWidget> {
#override
Widget build(BuildContext context) {
return Consumer<CurrenLTabsProvider>(
builder: (context, tabsProvider, child) {
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: tabsProvider.availableTabItems.length,
itemBuilder: (context, index) {
final item = tabsProvider.availableTabItems[index];
return ListTile(
title: Text(item.title),
leading: Checkbox(
value: tabsProvider.selectedTabs.contains(item.id),
onChanged: (value) {
if (value==true) {
setState(() {
tabsProvider.selectedTabs.add(item.id);
tabsProvider.doNotifyListeners();
});
} else {
setState(() {
tabsProvider.selectedTabs.remove(item.id);
tabsProvider.doNotifyListeners();
});
}
},
),
);
},
),
),
],
);
}
);
}
}
class TabWidget extends StatefulWidget {
const TabWidget(this.allItems, this.selectedTabs, {Key? key}) : super(key: key);
final List<MyTabItem> allItems;
final List<int> selectedTabs;
#override
_TabWidgetState createState() => _TabWidgetState();
}
class _TabWidgetState extends State<TabWidget> with TickerProviderStateMixin {
late TabController _tabController;
#override
void initState() {
_tabController = TabController(length: widget.selectedTabs.length, vsync: this);
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.selectedTabs.isEmpty) {
return Container(
padding: const EdgeInsets.all(20),
child: const Text("Select some tabs to be available."),
);
} // else ..
// re-initialise here, so changes made in other widgets are picked up when the widget is rebuilt
_tabController = TabController(length: widget.selectedTabs.length, vsync: this);
var tabs = <Widget>[];
List<Widget> tabBody = [];
// loop through all available tabs
for (var i = 0; i < widget.allItems.length; i++) {
// if it is selected, then show it
if (widget.selectedTabs.contains(widget.allItems[i].id)) {
tabs.add( Tab(text: widget.allItems[i].title) );
tabBody.add( widget.allItems[i].widget );
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.black54,
tabs: tabs,
controller: _tabController,
indicatorSize: TabBarIndicatorSize.tab,
),
Expanded(
child: TabBarView(
children: tabBody,
controller: _tabController,
),
),
]
);
}
}
Why does the TabBar reset to the 1st entry, while the TabBarView resets to the 2nd entry?
And what can I do to fix it so they both reset to the 1st entry?
Provide UniqueKey()on TabWidget(). It solves the issue for this code-snippet. It will be like
TabWidget(
tp.availableTabItems,
tp._selectedTabIds,
key: UniqueKey(),
),

Flutter can't keep the state of tabs using PageTransitionSwitcher

I am struggling with animations package and I want to use animation with BottomNavigationBar.
Without animation, I can save my state using IndexedStack.
If I wrap IndexedStack inside PageTransitionSwitcher it doesn't work. In particular:
animations are not showing but state is kept
if I use key property of my IndexedStack, animations are showing but state is not working.
How can i fix it? I don't know how to set up keys.
Thank you very much!!
class MainScreen extends StatefulWidget {
static String id = 'loading_screen';
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(PrimoTab());
pageList.add(SecondoTab());
pageList.add(TerzoTab());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bottom tab'),
),
body: PageTransitionSwitcher(
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
child: child,
transitionType: SharedAxisTransitionType.horizontal,
);
},
child: IndexedStack(
index: _selectedPage,
children: pageList,
//key: ValueKey<int>(_selectedPage), NOT WORKING
),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.directions_car),
label: 'First Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.airplanemode_active),
label: 'Second Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.directions_bike),
label: 'Third Page',
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.lightGreen,
unselectedItemColor: Colors.lightBlueAccent,
onTap: _onItemTapped,
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
Each tab is just a column with two text widgets, a button, and a text counter (for testing the state of each tab):
class PrimoTab extends StatefulWidget {
#override
_PrimoTabState createState() => _PrimoTabState();
}
class _PrimoTabState extends State<PrimoTab> {
int cont = -1;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'TAB 1 - TEXT 1',
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'TAB 1 - TEXT 2',
),
),
FlatButton(
onPressed: () {
setState(() {
cont++;
});
},
child: Text("CLICK"),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Valore contatore $cont',
),
),
],
),
);
}
}
UPDATE 1: Using just
pageList[_selectedPage],
instead of
IndexedStack(
...
)
but not working (animations ok but state is not kept)
UPDATE 2 WITH SOLUTION (main.dart):
void main() {
runApp(
MaterialApp(
home: MainScreen(),
),
);
}
class MainScreen extends StatefulWidget {
static String id = 'loading_screen';
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(PrimoTab());
pageList.add(SecondoTab());
pageList.add(TerzoTab());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bottom tab'),
),
body: AnimatedIndexedStack(
index: _selectedPage,
children: pageList,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.directions_car),
label: 'First Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.airplanemode_active),
label: 'Second Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.directions_bike),
label: 'Third Page',
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.lightGreen,
unselectedItemColor: Colors.lightBlueAccent,
onTap: _onItemTapped,
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
class AnimatedIndexedStack extends StatefulWidget {
final int index;
final List<Widget> children;
const AnimatedIndexedStack({
Key key,
this.index,
this.children,
}) : super(key: key);
#override
_AnimatedIndexedStackState createState() => _AnimatedIndexedStackState();
}
class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
int _index;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 150),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.ease,
),
);
_index = widget.index;
_controller.forward();
super.initState();
}
#override
void didUpdateWidget(AnimatedIndexedStack oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.index != _index) {
_controller.reverse().then((_) {
setState(() => _index = widget.index);
_controller.forward();
});
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _controller.value,
child: Transform.scale(
scale: 1.015 - (_controller.value * 0.015),
child: child,
),
);
},
child: IndexedStack(
index: _index,
children: widget.children,
),
);
}
}
There are few ways to fix your situation. None of them is perfectly simple and there already lots of discussion here and here, trying to merge IndexedStack with PageTransitionSwitcher. No solution so far I saw.
I collect following possible ways to achieve this:
Store state somewhere else and pass into child. I haven't seen any method can stop PageTransitionSwitcher from rebuilding child widget. If you don't mine the child widget rebuild, it may be the most straight forward method to do with this.
Use Custom IndexedStack with animation. like this and this. It works well with the feature in IndexStack that children won't rebuild, but the animation is not as good as PageTransitionSwitcher and it can only show 1 widget in one time.
This was the closest solution I found. You get little animation while preserving the state of the page
class AnimatedIndexedStack extends StatefulWidget {
final int index;
final List<Widget> children;
const AnimatedIndexedStack({
Key key,
this.index,
this.children,
}) : super(key: key);
#override
_AnimatedIndexedStackState createState() => _AnimatedIndexedStackState();
}
class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
int _index;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 150),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.ease,
),
);
_index = widget.index;
_controller.forward();
super.initState();
}
#override
void didUpdateWidget(AnimatedIndexedStack oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.index != _index) {
_controller.reverse().then((_) {
setState(() => _index = widget.index);
_controller.forward();
});
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _controller.value,
child: Transform.scale(
scale: 1.015 - (_controller.value * 0.015),
child: child,
),
);
},
child: IndexedStack(
index: _index,
children: widget.children,
),
);
}
}

How load content of Page just when page shown in screen in flutter

Here i have bottomNavigationBar to move between Pages so when home page loaded all classes will loaded and run initState , but in initState i have http requests to get data ; so all http request will run when i first show home page ...
i need to run http request of that page only when user swipe to that page
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: IndexedStack(
index: _currentIndex,
children: [
Partner(),
Partners(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
title: Text('Partner'),
),
BottomNavigationBarItem(
title: Text('Partners'),
),
],
),
);
}
}
you can use TabBarView instead as following:
class _HomeState extends State<Home> with SingleTickerProviderStateMixin { // necessary
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: _tabController,
children: <Widget>[Partner(),Partners()],
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (int index) {
_tabController.animateTo(index);
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
title: Text('Partner'),
),
BottomNavigationBarItem(
title: Text('Partners'),
),
],
),
);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}

flutter PageView inside TabBarView: scrolling to next tab at the end of page

I have 3 tabs and each tab has a PageView inside.
At the end of the PageView, I want to be able to scroll to the next tab.
Is there a way I can do TabBar scroll instead of PageView scroll if there's no more page to the direction? (only left or right scroll)
Here's the sample code.
When I scroll to right at the last page of the 1st tab, I want to see the first page of the 2nd tab.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: ScrollableTabsDemo());
}
}
class _Page {
const _Page({ this.icon, this.text });
final IconData icon;
final String text;
}
const List<_Page> _allPages = <_Page>[
_Page(icon: Icons.grade, text: 'TRIUMPH'),
_Page(icon: Icons.playlist_add, text: 'NOTE'),
_Page(icon: Icons.check_circle, text: 'SUCCESS'),
];
class ScrollableTabsDemo extends StatefulWidget {
static const String routeName = '/material/scrollable-tabs';
#override
ScrollableTabsDemoState createState() => ScrollableTabsDemoState();
}
class ScrollableTabsDemoState extends State<ScrollableTabsDemo> with SingleTickerProviderStateMixin {
TabController _controller;
#override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: _allPages.length);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final Color iconColor = Theme.of(context).accentColor;
return Scaffold(
appBar: AppBar(
title: const Text('Scrollable tabs'),
bottom: TabBar(
controller: _controller,
isScrollable: true,
tabs: _allPages.map<Tab>((_Page page) {
return Tab(text: page.text, icon: Icon(page.icon));
}).toList(),
),
),
body: TabBarView(
controller: _controller,
children: _allPages.map<Widget>((_Page page) {
return SafeArea(
top: false,
bottom: false,
child: PageView.builder(
itemBuilder: (context, position)
{
return Container(child: Center(child: Text(position.toString())));
},
itemCount: 5,
),
);
}).toList(),
),
);
}
}
Add to the pageBuilder the onPageChange param. Then check if its the last page, if so, animate the tabController to the nextPage.
onPageChanged: (page) {
if (page == _allPages.length &&
(_controller.index + 1) < _controller.length) {
_controller.animateTo(_controller.index + 1);
}
},
itemCount: _allPages.length + 1,

Flutter - Modify AppBar from a page

So I have a Flutter application with multiple pages, this is done via a PageView. Before this page view I create my AppBar so it is persistent at the top of the application and doesn't animate when scrolling between pages.
I then want on one of the pages to create a bottom App bar, but for that I need to access the App bar element, however I have no idea how to do this.
This is the main class, the page I am trying to edit the app bar on is PlanPage.
final GoogleSignIn googleSignIn = GoogleSignIn();
final FirebaseAuth auth = FirebaseAuth.instance;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
home: _handleCurrentScreen()
);
}
Widget _handleCurrentScreen() {
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
print(snapshot);
if (snapshot.connectionState == ConnectionState.waiting) {
return SplashPage();
} else {
if (snapshot.hasData) {
return Home();
}
return LoginPage();
}
}
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State<Home> {
PageController _pageController;
PreferredSizeWidget bottomBar;
int _page = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: bottomBar,
),
body: PageView(
children: [
Container(
child: SafeArea(
child: RecipesPage()
),
),
Container(
child: SafeArea(
child: PlanPage()
),
),
Container(
child: SafeArea(
child: ShoppingListPage()
),
),
Container(
child: SafeArea(
child: ExplorePage()
),
),
],
/// Specify the page controller
controller: _pageController,
onPageChanged: onPageChanged
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.book),
title: Text('Recipes')
),
BottomNavigationBarItem(
icon: Icon(Icons.event),
title: Text('Plan')
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text('Shopping List')
),
BottomNavigationBarItem(
icon: Icon(Icons.public),
title: Text("Explore"),
),
],
onTap: navigationTapped,
currentIndex: _page,
),
);
}
void onPageChanged(int page){
setState((){
this._page = page;
});
}
void setBottomAppBar(PreferredSizeWidget appBar) {
this.bottomBar = appBar;
print("setBottomAppBar: "+ appBar.toString());
}
/// Called when the user presses on of the
/// [BottomNavigationBarItem] with corresponding
/// page index
void navigationTapped(int page){
// Animating to the page.
// You can use whatever duration and curve you like
_pageController.animateToPage(
page,
duration: const Duration(milliseconds: 300),
curve: Curves.ease
);
}
#override
void initState() {
super.initState();
initializeDateFormatting();
_pageController = PageController();
}
#override
void dispose(){
super.dispose();
_pageController.dispose();
}
}
The PlanPage class looks like this
class PlanPage extends StatefulWidget {
var homeState;
PlanPage(this.homeState);
#override
State<StatefulWidget> createState() {
return _PlanState(homeState);
}
}
class _PlanState extends State<PlanPage> with AutomaticKeepAliveClientMixin<PlanPage>, SingleTickerProviderStateMixin {
var homeState;
TabController _tabController;
_PlanState(this.homeState);
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
//homeState.setBottomAppBar(_buildTabBar());
return Scaffold(
appBar: AppBar(
bottom: _buildTabBar(),
),
body: TabBarView(
controller: _tabController,
children: Plan.now().days.map((day) {
return ListView.builder(
itemCount: MealType.values.length,
itemBuilder: (BuildContext context, int index){
var mealType = MealType.values[index];
return Column(
children: <Widget>[
Text(
mealType.toString().substring(mealType.toString().indexOf('.')+1),
style: TextStyle(
//decoration: TextDecoration.underline,
fontSize: 30.0,
fontWeight: FontWeight.bold
),
),
Column(
children: day.meals.where((meal) => meal.mealType == mealType).map((meal) {
return RecipeCard(meal.recipe);
}).toList(),
)
],
);
}
);
}).toList(),
)
);
}
Widget _buildTabBar() {
return TabBar(
controller: _tabController,
isScrollable: true,
tabs: List.generate(Plan.now().days.length,(index) {
return Tab(
child: Column(
children: <Widget>[
Text(DateFormat.E().format(Plan.now().days[index].day)),
Text(DateFormat('d/M').format(Plan.now().days[index].day)),
],
),
);
}, growable: true),
);
}
#override
void initState() {
super.initState();
_tabController = new TabController(
length: Plan.now().days.length,
vsync: this,
initialIndex: 1
);
}
}
However the way it works now, makes it show 2 app bars.[
Usually it's a not a best practice to have two nested scrollable areas. Same for two nested Scaffolds.
That said, you can listen to page changes ( _pageController.addListener(listener) ) to update a page state property, and build a different AppBar.bottom (in the Home widget, so you can remove the Scaffold in PlanPage) depending on the page the user is viewing.
-EDIT-
In your Home widget you can add a listener to the _pageController like so:
void initState() {
super.initState();
_pageController = PageController()
..addListener(() {
setState(() {});
});
}
to have your widget rebuilt every time the user scrolls within your PageView. The setState call with an empty function might looks confusing, but it simply allows you to have the widget rebuilt when _pageController.page changes, which is not the default behavior. You could also have a page state property and update it in the setState call to reflect the _pageController.page property, but the result would be the same.
This way you can build a different AppBar.bottom depending on the _pageController.page:
// in your build function
final bottomAppBar = _pageController.page == 2 ? TabBar(...) : null;
final appBar = AppBar(
bottom: bottomAppBar,
...
);
return Scaffold(
appBar: appBar,
...
);