I want implement feature that when user taps BottomNavigationBarItem
pop to root if current page index equal to tapped index like normal iOS app.
I tried like below. I set all root pages route in MaterialApp
and in HomeScreen
if currentIndex
equal to index
, popUntil
to root page.
However error says
flutter: The following StateError was thrown while handling a gesture:
flutter: Bad state: Future already completed
How could I make this work?
Code:
// MyApp class
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
routes: <String, WidgetBuilder>{
'/Page1': (BuildContext context) => new Page1(),
'/Page2': (BuildContext context) => new Page2(),
'/Page3': (BuildContext context) => new Page3(),
'/Page4': (BuildContext context) => new Page4(),
},
title: 'Flutter Example',
home: new HomeScreen(),
);
}
}
// MyHome class
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final List<StatelessWidget> pages = [
new Page1(),
new Page2(),
new Page3(),
new Page3(),
];
final List<String> routes = [
'/Page1',
'/Page2',
'/Page3',
'/Page4',
];
int currentIndex = 0;
@override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () => new Future<bool>.value(true),
child: new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
onTap: (index) {
if (index == currentIndex) {
Navigator.popUntil(context, ModalRoute.withName(routes[index]));
}
currentIndex = index;
},
backgroundColor: Colors.white,
activeColor: Colors.blue,
inactiveColor: Colors.grey,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.looks_one),
title: Text('Page1'),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two),
title: Text('Page2'),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_3),
title: Text('Page3'),
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_4),
title: Text('Page4'),
),
],
),
tabBuilder: (BuildContext context, int index) {
return new DefaultTextStyle(
style: const TextStyle(
fontFamily: '.SF UI Text',
fontSize: 17.0,
color: CupertinoColors.black,
),
child: new CupertinoTabView(
builder: (BuildContext context) {
return pages[index];
},
),
);
},
),
);
}
}
First, you have to declare one GlobalKey<NavigatorState> per tab and then pass it to the corresponding CupertinoTabView constructors. Then, in the onTap method of your CupertinoTabBar , you can and pop to root with firstTabNavKey. currentState. popUntil((r) => r.
This example uses MaterialPageRoute , which provides the transition animation and handles route changes: import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'My App', home: Main(), )); } class Main extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( ...
It's now possible to achieve this behavior since Flutter v1.1.1 that added navigatorKey to CupertinoTabView (https://github.com/flutter/flutter/commit/65df90d8b5e1145e1022c365bb0465aa8c30dcdf)
First, you have to declare one GlobalKey<NavigatorState>
per tab and then pass it to the corresponding CupertinoTabView
constructors.
Then, in the onTap
method of your CupertinoTabBar
, you can and pop to root with firstTabNavKey.currentState.popUntil((r) => r.isFirst)
if the user is tapping while staying in the same tab.
Full example below :
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int currentIndex = 0;
final GlobalKey<NavigatorState> firstTabNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> secondTabNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> thirdTabNavKey = GlobalKey<NavigatorState>();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: CupertinoTabScaffold(
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
navigatorKey: firstTabNavKey,
builder: (BuildContext context) => FirstTab(),
);
break;
case 1:
return CupertinoTabView(
navigatorKey: secondTabNavKey,
builder: (BuildContext context) => SecondTab(),
);
break;
case 2:
return CupertinoTabView(
navigatorKey: thirdTabNavKey,
builder: (BuildContext context) => ThirdTab(),
);
break;
}
return null;
},
tabBar: CupertinoTabBar(
items: <BottomNavigationBarItem> [
BottomNavigationBarItem(
title: Text('Tab 1'),
),
BottomNavigationBarItem(
title: Text('Tab 2'),
),
BottomNavigationBarItem(
title: Text('Tab 3'),
),
],
onTap: (index){
// back home only if not switching tab
if(currentIndex == index) {
switch (index) {
case 0:
firstTabNavKey.currentState.popUntil((r) => r.isFirst);
break;
case 1:
secondTabNavKey.currentState.popUntil((r) => r.isFirst);
break;
case 2:
thirdTabNavKey.currentState.popUntil((r) => r.isFirst);
break;
}
}
currentIndex = index;
},
),
),
);
}
}
you can achieve the same effect with pushAndRemoveUntil
(if i'm understanding your need correctly).
final PageRouteBuilder _homeRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, _, __) {
return HomeScreen();
},
);
void _goHome() {
Navigator.pushAndRemoveUntil(context, _homeRoute, (Route<dynamic> r) => false);
}
this has the added benefit of being able to utilize PageRouteBuilder's other properties.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With