Get current route name of CupertinoTabView in Flutter? - flutter

I'm using CupertinoTabScaffold and CupertinoTabView to build navigation bottom bar in my App. For one CupertinoTabView I go to others pushed routes name, I would like to get the current name of a CupertinoTabView, but I get Null
I define the routes in main like that
CupertinoApp(
home: MyApp(),
title: 'machin',
routes: appRoutes,)
final appRoutes = {
'/pushedName': (context) => PushedName(),
};
MyApp class //
final GlobalKey<NavigatorState> profileTabNavKey =
GlobalKey<NavigatorState>();
CupertinoTabScaffold(
tabBar: CupertinoTabBar(
activeColor: Color(0xff077018),
border: Border.all(color: Color(0xffffffff)),
currentIndex: widget.currentIndex,
onTap: (index) {},
items: <BottomNavigationBarItem>[....],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
navigatorKey: profileTabNavKey,
routes: appRoutes,
builder: (BuildContext context) =>
SettingsView());
break;
default:
return HomePage();
}
},
),
In the SettingsView I pushed a named route by using
Navigator.pushNamed(context, '/pushedName')
I tried to get the route name in the my app class by using
print(ModalRoute.of(profileTabNavKey.currentContext).settings.name);
nb: in the pushedName View i get it perfectly any help , thanks in advance

Just use the BuildContext from the build widget to get the ModalRoute data :
ModalRoute.of(context).settings.name
Working example :
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: false,
theme: CupertinoTheme.of(context).copyWith(
brightness: Brightness.light,
),
home: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'Home',
icon: Icon(CupertinoIcons.home),
),
BottomNavigationBarItem(
label: 'Setting',
icon: Icon(CupertinoIcons.settings),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 1:
return CupertinoTabView(
routes: <String, WidgetBuilder>{
'/setting': (context) => SettingsPage(),
'/setting/2': (context) => SettingsPage(2),
'/setting/2/3': (context) => SettingsPage(3),
},
builder: (context) => SettingsPage(),
);
break;
default:
return Center(
child: Text('Home page'),
);
}
},
);
}
}
class SettingsPage extends StatelessWidget {
final int index;
SettingsPage([this.index = 1]);
#override
Widget build(BuildContext context) {
// here we go to get the current route name
print(ModalRoute.of(context).settings.name);
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5),
middle: Text(index > 1 ? 'Settings page - $index' : 'Settings page'),
),
child: Center(
child: CupertinoButton.filled(
child: Text('Go'),
onPressed: () {
if (index == 1) {
Navigator.pushNamed(context, '/setting/2');
} else if (index == 2) {
Navigator.pushNamed(context, '/setting/2/3');
}
},
),
),
);
}
}
Go to Dartpad

I found a solution that doesn't sound like best practice, but it works!
Instead of using ModalRouter and other libraries like navigation_history_observer, I used Navigator.popUntil and blocked the popup from getting the current route from the argument and assigned it to a variable.
WillPopScope(
onWillPop: () async {
String currentRoute;
navigatorKeys[_tabController.index].currentState.popUntil((route) {
currentRoute = route.settings.name;
return true;
});
if (currentRoute == '/') {
return Future.value(false);
} else {
return !await navigatorKeys[_tabController.index]
.currentState
.maybePop();
}
},
// ...
);

Related

Flutter: State management error with go_router and riverpod

I realise a Flutter app and I'm a really beginner. I'm using Riverpod for the state management and go_router for the routing. I try to implement a navbar visible only if you are logged. But I think I have a state management issue: when I press a navbar button, nothing happened (no console error neither) but If I logout and login or if I modify my code and save, my Emulator go to the right page.
I try to wrap my pages in a bigger Scaffold, to persist the AppBar and NavBar.
Here is my main.dart:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
// This widgets is the root of your application.
#override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
title: 'Ludocal 2',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
),
debugShowCheckedModeBanner: false,
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
);
}
}
My router:
List<GoRoute> get _routes => [
GoRoute(
name: 'login',
builder: (context, state) => const LoginScreen(),
path: '/login'),
GoRoute(
path: '/:screenName(home|game|event|profile)',
builder: (BuildContext context, GoRouterState state) {
final String screenName = state.params['screenName']!;
return LoggedScreen(screenName: screenName);
})
];
My logged_screen.dart wrapping my other screens:
class LoggedScreen extends HookConsumerWidget {
const LoggedScreen({super.key, required this.screenName});
final String screenName;
#override
Widget build(BuildContext context, WidgetRef ref) {
Future.delayed(Duration.zero, () {
switch (ref.read(indexProvider.state).state) {
case 0:
context.go('/home');
break;
case 1:
context.go('/game');
break;
case 2:
context.go('/event');
break;
case 3:
context.go('/profile');
break;
}
});
return Scaffold(
appBar: AppBar(
title: Text("Ludocal 2"),
backgroundColor: Colors.deepOrangeAccent,
actions: [
TextButton.icon(
icon: Icon(
Icons.logout_rounded,
color: Colors.white,
),
label: Text('', style: TextStyle(color: Colors.white)),
onPressed: () async {
ref.read(loginControllerProvider.notifier).signOut();
},
),
]),
body: BodyTab(screenName: screenName),
bottomNavigationBar: const BottomTab(),
);
}
}
class BodyTab extends ConsumerWidget {
const BodyTab({super.key, required this.screenName});
final String screenName;
#override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
children: [
Expanded(
child: screenName == 'home'
? const HomeScreen()
: screenName == 'game'
? const GameScreen()
: screenName == 'event'
? const EventScreen()
: const ProfileScreen()),
],
);
}
}
class BottomTab extends ConsumerWidget {
const BottomTab({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
return BottomNavigationBar(
currentIndex: ref.read(indexProvider.state).state,
onTap: (int index) => ref.read(indexProvider.state).state = index,
backgroundColor: Colors.deepOrangeAccent,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.emoji_emotions),
label: 'Game',
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_rounded),
label: 'Event',
),
],
);
}
}
final indexProvider = StateProvider<int>((ref) {
return 0;
});
The login_controller.dart:
class LoginController extends StateNotifier<LoginState> {
LoginController(this.ref) : super(const LoginStateInitial());
final Ref ref;
void login(String email, String password) async {
state = const LoginStateLoading();
try {
await ref.read(authRepositoryProvider).signInWithEmailAndPassword(
email,
password,
);
state = const LoginStateSuccess();
} catch (e) {
state = LoginStateError(e.toString());
}
}
void signOut() async {
await ref.read(authRepositoryProvider).signOut();
state = const LoginStateInitial();
}
}
final loginControllerProvider =
StateNotifierProvider<LoginController, LoginState>((ref) {
return LoginController(ref);
});
Appreciate if someone can advise. Thank you in advance!
For navigation you need to use listen like below.
ref.listen(indexProvider, (previous, next) {
switch (next) {
case 0:
context.go('/home');
break;
case 1:
context.go('/game');
break;
case 2:
context.go('/event');
break;
case 3:
context.go('/profile');
break;
}
});

Flutter Getx How to navigate to other page in StreamBuilder

I am using firebase_auth to signup and login.
I am facing a problem with Streambuilder.
I would like to show page depends on User Logged in or not. It seems working fine.
But, the problem is that I can't use Get.off('/app'); in StreamBuilder and FutureBuilder.
if I can't use Getx.off('/app'); user can get back just pressing the back button, and
I would like to avoid this, so I am trying to use Get.off page route.
But, as vs code shows that FutureBuilder and StreamBuilder's builder return Widget,
and I have no idea how to code.
Any suggestion for this matter?
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Karrot Market Clone',
theme: ThemeData(
primaryColor: Colors.black,
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
appBarTheme: AppBarTheme(
color: Colors.white,
),
),
initialBinding: InitBinding(),
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => BridgeFirebase(),
),
GetPage(
name: '/bridge_page',
page: () => BridgePage(),
),
GetPage(
name: '/app',
page: () => App(),
transition: Transition.rightToLeft,
),
GetPage(
name: '/start',
page: () => Start(),
),
GetPage(
name: '/login',
page: () => Login(),
transition: Transition.rightToLeft,
),
GetPage(
name: '/signup',
page: () => SignUp(),
transition: Transition.rightToLeft,
),
],
);
}
}
class BridgeFirebase extends StatelessWidget {
const BridgeFirebase({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Firebase.initializeApp(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Firebase load fail'));
}
if (snapshot.connectionState == ConnectionState.done) {
return BridgePage();
}
return Center(
child: CircularProgressIndicator(
color: ColorsKM.primary,
),
);
},
);
}
}
class BridgePage extends StatelessWidget {
const BridgePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Splash();
}
if (snapshot.hasData) {
return App();
} else {
return Start();
}
},
);
}
}
// app.dart
class App extends GetView<AppController> {
const App({Key? key}) : super(key: key);
Widget _bodyWidget() {
switch (RouteName.values[controller.currentIndex.value]) {
case RouteName.HOME:
return Home();
break;
case RouteName.MYLOCAL:
return MyLocal();
break;
case RouteName.NEARBY:
return Nearby();
break;
case RouteName.CHATS:
return Chats();
break;
case RouteName.MYKARROT:
return MyKarrot();
break;
}
return Container();
}
BottomNavigationBarItem _bottomNavigationBarItem(
String iconName, String label) {
return BottomNavigationBarItem(
icon: Padding(
padding: EdgeInsets.only(bottom: 5.0),
child: SvgPicture.asset('assets/svg/${iconName}_off.svg', width: 22),
),
activeIcon: Padding(
padding: EdgeInsets.only(bottom: 5.0),
child: SvgPicture.asset('assets/svg/${iconName}_on.svg', width: 22),
),
label: label,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(
() {
return _bodyWidget();
},
),
bottomNavigationBar: Obx(
() => BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: controller.currentIndex.value,
selectedFontSize: 12.0,
showSelectedLabels: true,
selectedItemColor: Colors.black,
selectedLabelStyle: TextStyle(color: Colors.black),
onTap: controller.changePageIndex,
items: [
_bottomNavigationBarItem('home', 'home'),
_bottomNavigationBarItem('notes', 'neighbor'),
_bottomNavigationBarItem('location', 'nearby'),
_bottomNavigationBarItem('chat', 'chat'),
_bottomNavigationBarItem('user', 'my karrot'),
],
),
),
);
}
}
// app_controller.dart
enum RouteName {
HOME,
MYLOCAL,
NEARBY,
CHATS,
MYKARROT,
}
class AppController extends GetxService {
static AppController get to => Get.find();
late RxInt currentIndex = 0.obs;
void changePageIndex(int index) {
currentIndex(index);
}
}
Because the build method is not completed when you navigate in Streambuilder so you can delay some time to the build method complete then navigate. do this when you want to navigate in Streambuilder :
Future.delayed(Duration.zero).then((value) => Get.off('/app'));
or just navigate in your controller or bloc class.

Flutter Navigation doesn't pop Navigator stack on back press

I'm using Navigator for named routes to switch pages on screen with a BottomNavigationBar. On Page 3, I can navigate to Page 4 by NavigationKey.currentState.pushNamed(Page4Route).
On Page 4, I'm able to navigate back to Page 3 by calling NavigationKey.currentState.pop, but pressing the back button of the device closes the app instead. Any idea why the back button doesn't pop the current screen on the Navigation stack? How can I handle this better?
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: navigationKey,
initialRoute: Pages.home,
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
switch (settings.name) {
case Pages.home:
builder = (BuildContext context) => _page1();
break;
case Pages.page1:
builder = (BuildContext context) => _page1();
break;
case Pages.page2:
builder = (BuildContext context) => _page2();
break;
case Pages.page3:
builder = (BuildContext context) => _page3();
break;
case Pages.page4:
builder = (BuildContext context) => Page4Screen(navigatorKey: navigationKey);
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
bottomNavigationBar: BottomNavigationBar(),
...
);
}
Demo
Minimal Repro
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nested Routing Demo',
home: HomePage(),
);
}
}
class Pages{
static const home = '/';
static const page1 = '/page1';
static const page2 = '/page2';
static const page3 = '/page3';
static const page4 = '/page4';
}
class HomePage extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<HomePage> {
final GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();
var _currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: navigationKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
switch (settings.name) {
case Pages.home:
builder = (BuildContext context) => _page1();
break;
case Pages.page1:
builder = (BuildContext context) => _page1();
break;
case Pages.page2:
builder = (BuildContext context) => _page2();
break;
case Pages.page3:
builder = (BuildContext context) => _page3();
break;
case Pages.page4:
builder = (BuildContext context) => Page4Screen(navigatorKey: navigationKey);
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
// You can also return a PageRouteBuilder and
// define custom transitions between pages
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.mood),
label: 'Page 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.connect_without_contact),
label: 'Page 2',
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
label: 'Page 3',
),
],
currentIndex: _currentPage,
// selectedItemColor: Colors.amber[800],
onTap: (value) {
/// Update page if a different tab from the current was clicked
if (value != _currentPage)
setState(() {
_currentPage = value;
switch (value) {
case 0:
navigationKey.currentState!
.pushReplacementNamed(Pages.page1);
break;
case 1:
navigationKey.currentState!
.pushReplacementNamed(Pages.page2);
break;
case 2:
navigationKey.currentState!
.pushReplacementNamed(Pages.page3);
break;
default:
/// TODO Error 404 page
throw Exception('Invalid route: $value');
}
});
},
),
);
}
Widget _page1() {
return Scaffold(
body: Container(
color: Colors.lightBlueAccent,
child: Center(
child: Text('Page 1'),
),
),
);
}
Widget _page2() {
return Scaffold(
body: Container(
color: Colors.orangeAccent,
child: Center(
child: Text('Page2'),
),
),
);
}
Widget _page3() {
return Scaffold(
body: Container(
color: Colors.lightGreenAccent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 3'),
ElevatedButton(onPressed: (){
navigationKey.currentState!.pushNamed(Pages.page4);
}, child: Text('Page 4')),
],
),
),
),
);
}
}
class Page4Screen extends StatefulWidget {
Page4Screen({Key? key, required GlobalKey<NavigatorState> navigatorKey}) : _navigatorKey = navigatorKey, super(key: key);
final GlobalKey<NavigatorState> _navigatorKey;
#override
createState() => Page4State();
}
class Page4State extends State<Page4Screen>{
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(AppLocalizations.of(context)!.txtTitleMood),
// automaticallyImplyLeading: false,
// ),
body: Container(
color: Colors.redAccent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 4'),ElevatedButton(
// Within the SecondScreen widget
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack.
widget._navigatorKey.currentState!.pop();
},
child: Text('Go Back!'),
),
],
),
),
),
);
}
}
The Navigator widget does not handle back buttons by default and that's your job to do it if you have defined a Navigator widget. You can catch back press by WillPopScope widget. It takes a Future<bool> Function() which will be called whenever user wants to go back. If it returns false then your default Navigator which lies in MaterialApp will not pop the current route which simply is showing your HomePage in this case. So if your nested navigator has something to pop (like Page4) then it will pop that and will prevent your main Navigator from popping your HomePage.
class _HomeState extends State<HomePage> {
...
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => !(await navigationKey.currentState!.maybePop()),
child: Scaffold(
...
),
);
}
}

How do I make BottomNavigationBar page transition with Flutter's onWillpop?

I am using BottomNavigationBar in Flutter Project.
This question is for line 30 "//TODO: back to the FirstTab".
When a user is in the SecondTab or ThirdTab and the BackButton in the Android device is pressed, I want the user to go to the FirstTab.
Now, in onWillpopScope, there is a process to pop when you can. It is used when the user is in NextPage.
Then, when the user is not in the FirstTab (SecondTab or ThirdTab) and not in the NextTab, I want to move him to the FirstTab in onWillpopScope. (I want to force the BottomNavigationBar to switch.)
How should I describe it? Please tell me.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<GlobalKey<NavigatorState>> navigatorKeys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
];
#override
Widget build(BuildContext context) {
int currentIndex = 0;
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab = await navigatorKeys[currentIndex].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (currentIndex != 0) {
//TODO: back to the FirstTab
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(label: 'Home', icon: Icon(Icons.home)),
BottomNavigationBarItem(label: 'Search', icon: Icon(Icons.search)),
BottomNavigationBarItem(label: 'Setting', icon: Icon(Icons.settings)),
],
onTap: (index) {
// back home only if not switching tab
if (currentIndex == index) {
switch (index) {
case 0:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
case 1:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
case 2:
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
break;
}
}
currentIndex = index;
},
currentIndex: currentIndex,
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
navigatorKey: navigatorKeys[index],
builder: (BuildContext context) {
switch (index) {
case 0:
return FirstTab();
case 1:
return SecondTab();
case 2:
return ThirdTab();
default:
return FirstTab();
}
},
);
},
),
),
);
}
}
class FirstTab extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('first page now'),
),
backgroundColor: Colors.red[200],
child: Center(
child: CupertinoButton(
child: const Text('Next'),
onPressed: () {
Navigator.of(context).push(CupertinoPageRoute(builder: (context) => NextPage()));
},
),
),
);
}
}
//Different color from Firsttab
class SecondTab extends StatelessWidget {}
//Different color from Firsttab
class ThirdTab extends StatelessWidget {}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('second page now'),
),
backgroundColor: Colors.white,
child: Center(
child: CupertinoButton(
child: const Text('Back'),
onPressed: () {
Navigator.of(context).pop();
},
),
),
);
}
}
You can try to see it docs.
I'll resume here. First you need to create a controller
final CupertinoTabController _controller = CupertinoTabController();
and add to your CupertinoTabScaffold like this
CupertinoTabScaffold(
...
controller: _controller,
)
in the end you change the page like this:
_controller.index = 0,
(this is how get and set work in Flutter)
first move
int currentIndex = 0;
to class member
next
//TODO: back to the FirstTab
setState((){
currentIndex = 0;
});

Flutter pop to root when bottom navigation tapped

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];
},
),
);
},
),
);
}
}
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.
This simple code worked for me to go to the root
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
}
The following worked, where in my set up all routes are named:
Navigator.of(context)
.pushNamedAndRemoveUntil('homeScreen', (route) => false);
Thanks to answers from #blaneyneil and #Juanma Menendez