Flutter: Change Scroll Offset in a child through NavigationBar - flutter

I currently have a Homepage, which consists of a Scaffold, with a bottomAppBar for navigation:
The body has 5 pages, the first page is a feed, which consists of a ListView of Widgets.
What I want to do is same as Instagram has it:
when I scroll down the feed and I click the Feed Button on the Navigation Bar, then I want the ListView to scroll back to the top automatically.
This is part of my code:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: [
HomeFeed(),
Page2(),
...
].elementAt(_selectedIndex),
bottomNavigationBar: BottomAppBar(
child: Row(
children: <Widget>[
IconButton(
icon: FaIcon(FontAwesomeIcons.houseUser),
onPressed: (){
if (_selectedIndex == 0) {
//add logic to make the HomeFeed ListView scroll up
} else {
setState((){
_selectedIndex = 0;
});
}
},
IconButton(
icon: FaIcon(FontAwesomeIcons.compass),
onPressed: (){
setState((){
_selectedIndex = 1;
});
},
...
],
),
), //BottomAppBar
), //Scaffold
}
I know that if I had the code of the HomeFeed inside the Scaffold.body then I could just use a Scrollcontroller and the animateTo method. The problem is that the Homefeed is another stateful widget and even though setState is called when clicking the feed icon, the HomeFeed widget is not rebuilding.
I tried defining a Scrollcontroller in the Homepage and pass it to the HomeFeed but it did not work.
Can anyone help me with that?

You can set a GlobalKey for the state of the HomeFeed widget. Using this GlobalKey you can call the functions of the state of the HomeFeed widget.
Main code:
GlobalKey<HomeFeedState> feedKey = new GlobalKey<HomeFeedState>(); // this is new
#override
Widget build(BuildContext context) {
return Scaffold(
body: [
HomeFeed(key: feedKey), // this is new
Page3(),
].elementAt(_selectedIndex),
bottomNavigationBar: BottomAppBar(
child: Row(
children: <Widget>[
IconButton(
icon: FaIcon(FontAwesomeIcons.houseUser),
onPressed: (){
if (_selectedIndex == 0) {
feedKey.currentState.jumpUp(); // this is new
} else {
setState(() {
_selectedIndex = 0;
});
}
},
),
IconButton(
icon: FaIcon(FontAwesomeIcons.compass),
onPressed: (){
setState(() {
_selectedIndex = 1;
});
},
),
],
),
), //BottomAppBar
);
}
HomeFeed:
class HomeFeed extends StatefulWidget {
final GlobalKey<HomeFeedState> key; // this is new
HomeFeed({this.key}) : super(key: key); // this is new
#override
HomeFeedState createState() => HomeFeedState();
}
class HomeFeedState extends State<HomeFeed> {
var _scrollController = new ScrollController();
jumpUp() { // this will be called when tapped on the home icon
_scrollController.animateTo(0,
duration: Duration(seconds: 2), curve: Curves.ease);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
shrinkWrap: true,
controller: _scrollController,
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 300,
child: Card(
child: Center(
child: Text('$index'),
),
),
);
},
),
);
}
}

class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
int _selectedIndex = 0;
Widget homeWidget = HomeFeed();
PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: pageController,
children: <Widget>[
homeWidget,
Second(),
...
],
),
bottomNavigationBar: BottomAppBar(
child: Row(
children: <Widget>[
IconButton(
icon: FaIcon(FontAwesomeIcons.houseUser),
onPressed: (){
setState((){
homeWidget = HomeFeed();
_selectedIndex = 0;
pageController.jumpToPage(0);
});
},
IconButton(
icon: FaIcon(FontAwesomeIcons.compass),
onPressed: (){
setState((){
_selectedIndex = 1;
pageController.jumpToPage(1);
});
},
...
],
),
), //BottomAppBar
), //Scaffold
}
use like this

Related

Why doesn't BottomNavigatorBarItem currentIndex get updated?

I am new to flutter and I did find similar questions on SO but am too novice to understand the nuance so apologies if this question is too similar to an already asked one.
I have a BottomNavigationBar which has 3 icons (Home, Play and Create) which should navigate between these 3 routes/pages.
main.dart
routes: {
"/home": (context) => MyHomePage(title: "STFU"),
"/play": (context) => Play(),
"/create": (context) => Create(),
"/settings": (context) => Settings(),
},
I extracted my navbar into a custom class so my 3 separate pages could use it:
bottom-nav.dart
class MyBottomNavBar extends StatefulWidget {
MyBottomNavBar({
Key? key,
}) : super(key: key);
#override
State<MyBottomNavBar> createState() => _MyBottomNavBarState();
}
class _MyBottomNavBarState extends State<MyBottomNavBar> {
int _selectedIndex = 0;
void _onTapped(int index) => {
print("_onTapped called with index = $index"),
setState(
() => _selectedIndex = index,
)
};
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
backgroundColor: Colors.orangeAccent[100],
currentIndex: _selectedIndex,
onTap: (value) => {
print("value is $value"),
// find index and push that
_onTapped(value),
if (value == 0)
{Navigator.pushNamed(context, "/home")}
else if (value == 1)
{Navigator.pushNamed(context, "/play")}
else if (value == 2)
{Navigator.pushNamed(context, "/create")}
},
items: [
BottomNavigationBarItem(label: "Home", icon: Icon(Icons.home_filled)),
BottomNavigationBarItem(
label: "Play", icon: Icon(Icons.play_arrow_rounded)),
BottomNavigationBarItem(label: "Create", icon: Icon(Icons.create)),
],
);
}
}
so now i just set this MyBottomNavBar class to the bottomNavigationBar property of the Scaffold widget my inside Home page, Play page and Create page for eg.
home.dart
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Column(
children: [
Container(
padding: EdgeInsets.all(20.00),
child: Text("inside home"),
),
],
),
bottomNavigationBar: MyBottomNavBar(),
);
}
}
play.dart
class Play extends StatefulWidget {
const Play({super.key});
#override
State<Play> createState() => _PlayState();
}
class _PlayState extends State<Play> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text("inside play page"),
SizedBox(
height: 30.00,
),
Text("some text"),
],
),
),
bottomNavigationBar: MyBottomNavBar(),
);
}
}
The nav bar buttons work to switch between pages but for some reason the currentIndex value isn't getting updated and stays at 0 (i.e on the "Home" icon). When I debug it I can see _selectedIndex getting updated inside inside the _onTapped function which should update the currentIndex value but it doesn't appear to do so. Any help would be appreciated
What you have here is three different pages with their separate BottomNavBar class instance. Instead you should have a shared Scaffold and one bottomNavBar so that when you navigate bottomNavbar state does not reset.
You can use PageView to do this.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
}
List<Widget> pages = const [Home(), Play(), Create()];
final _pageController = PageController();
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
_pageController.jumpToPage(index);
_selectedIndex = index;
setState(() {});
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.play_arrow), label: 'Play'),
BottomNavigationBarItem(icon: Icon(Icons.create), label: 'Create'),
],
backgroundColor: Colors.greenAccent,
),
body: PageView(
controller: _pageController,
children: pages,
),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
return const Placeholder();
}
}
class Play extends StatelessWidget {
const Play({super.key});
#override
Widget build(BuildContext context) {
return const Placeholder();
}
}
class Create extends StatelessWidget {
const Create({super.key});
#override
Widget build(BuildContext context) {
return const Placeholder();
}
}

How to navigate pageview pages with overwrite back button in AppBar?

I have a pageview view and it works with sliding. But how do I integrate this back button as leading: Icon(backbutton), when navigating between forms in the pageview? Thanks
screen1.dart
import 'package:app/src/features/examples/components/body.dart';
class OnboardingExampleFlowPage extends StatelessWidget {
static String routeName = "/onboarding_example_flow";
const OnboardingExampleFlowPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 1,
backgroundColor: AppColors.monochromeWhite,
title: Text(context.l10n.buttonBack),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {},
),
),
body: const Body(),
);
}
}
Body has pageview:
body.dart
class _BodyState extends State<Body> {
int currentPage = 0;
final PageController controller = PageController();
#override
void dispose() {
super.dispose();
controller.dispose();
}
#override
Widget build(BuildContext context) {
final List<Widget> formPages = <Widget>[
ExampleContent01(controller: controller),
ExampleContent02(controller: controller),
ExampleContent03(controller: controller),
ExampleContent04(controller: controller),
];
return SafeArea(
child: SizedBox(
child: Column(
children: [
const SizedBox(height: 6),
AppStepper(
currentPage: currentPage,
length: formPages.length,
noSkip: true,
),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(20),
),
child: PageView(
controller: controller,
onPageChanged: (value) => setState(() => currentPage = value),
children: formPages,
),
),
),
],
),
),
);
}
These forms: There are contents in ExampleScreens, but I did not add their code because there are AppBar and Pageview in the code I added.
here is view: want to be able to go back inside pageview.
Thanks a lot!
Just move the controller up, to the parent widget, so it's possible to navigate the pages with it.
Check out the live demo on DartPad.
The code is going to be like the following:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const OnboardingExampleFlowPage(),
scrollBehavior: MyCustomScrollBehavior(),
debugShowCheckedModeBanner: false,
);
}
}
class OnboardingExampleFlowPage extends StatefulWidget {
static String routeName = "/onboarding_example_flow";
const OnboardingExampleFlowPage({Key? key}) : super(key: key);
#override
State<OnboardingExampleFlowPage> createState() =>
_OnboardingExampleFlowPageState();
}
class _OnboardingExampleFlowPageState extends State<OnboardingExampleFlowPage> {
final PageController controller = PageController();
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 1,
title: const Text('Back'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
controller.previousPage(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
},
),
),
body: Body(controller: controller),
);
}
}
class Body extends StatefulWidget {
const Body({super.key, required this.controller});
final PageController controller;
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
int currentPage = 0;
#override
Widget build(BuildContext context) {
const List<Widget> formPages = [
Center(child: Text('Page 1')),
Center(child: Text('Page 2')),
Center(child: Text('Page 3')),
Center(child: Text('Page 4')),
];
return SafeArea(
child: SizedBox(
child: Column(
children: [
const SizedBox(height: 6),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: PageView(
controller: widget.controller,
onPageChanged: (value) => setState(() => currentPage = value),
children: formPages,
),
),
),
],
),
),
);
}
}
// Enables scrolling with mouse dragging
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
Dont have body widget in separate file
Put it in the _OnboardingExampleFlowPageState instead.
And it is the _OnboardingExampleFlowPageState that should have controller and
currentIndex variables.
So on leading button click you'll do something like this:
onPressed: () {
if (currentPage > 0) {
controller.previousPage(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
setState(() {
currentPage--;
});
}
},

Flutter SnackBar is attached to wrong Scaffold

I have two screens where the second screen is pushed above the first with Navigator.push() and the second screen is partial transparent. I want to display a SnackBar, but it isn't really visible. It looks like the ScaffoldMessenger chooses the wrong of the two Scaffolds to attach the Snackbar. This leads to the effect that the SnackBar collides with the TextInput and it is also not fully visible. But this bad behavior is only the case as long as the soft keyboard is open. If the keyboard is closed, everything works fine. It seems like the open keyboard tells the ScaffoldMessenger to choose the Scaffold from the second screen to display the SnackBar.
How can I achieve that the SnackBar is shown normally in the sense of is attached to the Scaffold of screen 2 while the keyboard is open? The expected behavior is that the Snackbar isn't displayed transparent.
Keyboard open -> SnackBar is attached to Scaffold of screen 1 -> Bad
Keyboard closed -> SnackBar is attached to Scaffold of screen 2 -> Good
GIF showing the complete workflow
My code (fully executable)
import 'dart:io';
import 'package:keyboard_utils/keyboard_listener.dart';
import 'package:keyboard_utils/keyboard_utils.dart';
import 'package:flutter/material.dart' hide KeyboardListener;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => const MaterialApp(home: MyHomePage());
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Title')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[const Text('You have pushed the button this many times:'),
Text('$_counter', style: Theme.of(context).textTheme.headline4),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
opaque: false, // push route with transparency
pageBuilder: (context, animation, secondaryAnimation) => const Screen2(),
));
},
child: const Text('navigate'),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _counter++),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Screen2 extends StatefulWidget {
const Screen2({Key? key}) : super(key: key);
#override
State<Screen2> createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
final _keyboardUtils = KeyboardUtils();
late int _idKeyboardListener;
final focusNode = FocusNode();
bool isEmojiKeyboardVisible = false;
bool isKeyboardVisible = false;
double _keyboardHeight = 300;
#override
void initState() {
super.initState();
_idKeyboardListener = _keyboardUtils.add(
listener: KeyboardListener(
willHideKeyboard: () {
if (isKeyboardVisible) {
isKeyboardVisible = false;
isEmojiKeyboardVisible = false;
}
setState(() {}); // show correct Icon in IconButton
},
willShowKeyboard: (double keyboardHeight) async {
if (Platform.isAndroid) {
_keyboardHeight = keyboardHeight + WidgetsBinding.instance.window.viewPadding.top / WidgetsBinding.instance.window.devicePixelRatio;
} else {
_keyboardHeight = keyboardHeight;
}
isKeyboardVisible = true;
isEmojiKeyboardVisible = true;
setState(() {});
},
)
);
}
#override
void dispose() {
_keyboardUtils.unsubscribeListener(subscribingId: _idKeyboardListener);
if (_keyboardUtils.canCallDispose()) {
_keyboardUtils.dispose();
}
focusNode.dispose();
super.dispose();
}
Future<void> onEmojiButtonPressed() async {
if(isEmojiKeyboardVisible){
if(isKeyboardVisible){
FocusManager.instance.primaryFocus?.unfocus();
isKeyboardVisible = false;
} else {
focusNode.unfocus();
await Future<void>.delayed(const Duration(milliseconds: 1));
if(!mounted) return;
FocusScope.of(context).requestFocus(focusNode);
}
} else {
assert(!isKeyboardVisible);
setState(() {
isEmojiKeyboardVisible = true;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold( // wrapping with ScaffoldMessenger does NOT fix this bug
backgroundColor: Colors.white.withOpacity(0.5),
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Expanded(
child: Container(
height: 200,
),
),
Row(
children: [
IconButton(
icon: Icon(isKeyboardVisible || !isEmojiKeyboardVisible ? Icons.emoji_emotions_outlined : Icons.keyboard_rounded),
onPressed: onEmojiButtonPressed,
),
Expanded(
child: TextField(
focusNode: focusNode,
),
),
IconButton(
icon: const Icon(Icons.send),
onPressed: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('A snack!'))),
),
],
),
],
),
),
),
Offstage(
offstage: !isEmojiKeyboardVisible,
child: SizedBox(
height: _keyboardHeight,
child: Container(color: Colors.red),
),
),
],
),
),
);
}
}
Dependencies
keyboard_utils: ^1.3.4
What I've tried
I tried to wrap the Scaffold of Screen2 with a ScaffoldMessenger. This doesn't fix my problem. In that case, no SnackBar was shown at all if the keyboard was open.
Edit: I also created an GitHub issue for that but I don't expect an answer soon: https://github.com/flutter/flutter/issues/105406#issuecomment-1147194647
Edit 2: A workaround for this issue is to use SnackBarBehaviod.floating and a bottom margin, for example:
SnackBar(
content: Text('A snack!'),
margin: EdgeInsets.only(bottom: 350.0),
behavior: SnackBarBehavior.floating,
)
But this is not a satisfying solution.

Flutter: PageView - The method 'jumpToPage' was called on null

I'm relatively new to flutter so it might be a very simple solution. I have implemented a drawer in a Scaffold() with a PageView as the body. I want to be able to jump to the PageView page.no as the Drawer list item is TappedOn.
I know I'm not initialising or setting something somewhere.
I've also tried keepPage: true, but doesn't make a difference.
class MyScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyScreenState();
}
}
class _MyScreenState extends State<MyScreen> {
var _currentIndex = 0;
PageController _pageController;
#override
void initState() {
super.initState();
_currentIndex = 0;
PageController(initialPage: _currentIndex, keepPage: false);
}
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: const Text('AppBar',),
elevation: 0.0,
leading: Builder(
builder: (context) => IconButton(
icon: new Icon(Icons.toc, size: 35.0),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
actions: <Widget>[
],
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero, // Important: Remove any padding from the ListView.
children: <Widget>[
Container(
height: 120,
child: DrawerHeader(
child: Text('Heading',),
),
),
ListTile(
title: Text('Home',),
onTap: () {
setState(() {
_currentIndex = 2;
});
Navigator.pop(context);
_pageController.jumpToPage(_currentIndex);
},
),
ListTile(),
ListTile(),
....
],
),
),
body: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentIndex = index;
});
},
children: <Widget>[
new Page1(),
new Page2(),
....
],
)
);
}
}
The above code is throwing the following error:
flutter: The following NoSuchMethodError was thrown while handling a
gesture:
flutter: The method 'jumpToPage' was called on null.
flutter: Receiver: null
flutter: Tried calling: jumpToPage(0)
Just make your PagController public like so: PageController pageController;.

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,
...
);