[FLUTTER]: Programmatically change tabs in the CustomNavigator from SecondScreen to FirstScreen - flutter

I'm currently making an app with bottom navigator. And I have troubles with navigating from SecondScreen to the FirstScreen, programmatically, inside the SecondScreen file. But I have no idea how to do it. Because I can't have the access to the CustomNavigatorState part of the CustomNavigator class.
My main.dart file:
import 'package:flutter/material.dart';
import './screens/custom_navigator.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Name',
home: Scaffold(
body: CustomNavigator(),
),
);
}
}
My custom_navigator.dart file, which includes CustomNavigator class and _CustomNavigatorState class:
import 'package:flutter/material.dart';
import './first_second.dart';
import './second_screen.dart';
import './third_screen.dart';
import '../widgets/tab_navigator.dart';
class CustomNavigator extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CustomNavigatorState();
}
class _CustomNavigatorState extends State<CustomNavigator> {
String _currentScreen = FirstScreen.route;
List<String> _screenKeys = [
FirstScreen.route,
SecondScreen.route,
ThirdScreen.route,
];
Map<String, GlobalKey<NavigatorState>> _navigatorKeys = {
FirstScreen.route: GlobalKey<NavigatorState>(),
SecondScreen.route: GlobalKey<NavigatorState>(),
ThirdScreen.route: GlobalKey<NavigatorState>(),
};
int _selectedIndex = 0;
void changeTab(String tabItem, int index) {
_selectedTab(tabItem, index);
}
void _selectedTab(String tabItem, int index) {
if (tabItem == _currentScreen) {
_navigatorKeys[tabItem].currentState.popUntil((route) => route.isFirst);
} else {
setState(() {
_currentScreen = _screenKeys[index];
_selectedIndex = index;
});
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await _navigatorKeys[_currentScreen].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentScreen != FirstScreen.route) {
_selectedTab(FirstScreen.route, 1);
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
resizeToAvoidBottomPadding: true,
body: Stack(
children: [
_buildOffstageNavigator(FirstScreen.route),
_buildOffstageNavigator(ScreenScreen.route),
_buildOffstageNavigator(ThirdScreen.route),
],
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
_selectedTab(_screenKeys[index], index);
},
currentIndex: _selectedIndex,
items: [
BottomNavigationBarItem(
label: 'First',
),
BottomNavigationBarItem(
label: 'Second',
),
BottomNavigationBarItem(
label: 'Third',
),
],
),
),
);
}
Widget _buildOffstageNavigator(String tabItem) {
return Offstage(
offstage: _currentScreen != tabItem,
child: TabNavigator(
navigatorKey: _navigatorKeys[tabItem],
tabItem: tabItem,
),
);
}
}
TabNavigator class, where the screens added.
import 'package:flutter/material.dart';
import '../screens/first_screen.dart';
import '../screens/second_screen.dart';
import '../screens/third_screen.dart';
class TabNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final String tabItem;
const TabNavigator({
Key key,
this.navigatorKey,
this.tabItem,
}) : super(key: key);
#override
Widget build(BuildContext context) {
Widget child;
if (tabItem == FirstScreen.route) {
child = FirstScreen();
} else if (tabItem == SecondScreen.route) {
child = SecondScreen();
} else if (tabItem == ThirdScreen.route) {
child = ThirdScreen();
}
return Navigator(
key: navigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => child,
);
},
);
}
}
I tried to navigate with Navigator.push and Navigator.pushNamed, but it navigates inside SecondScreen without changing the BottomNavigationTabBars.
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => SecondScreen(),
),
);
Navigator.of(context).pushNamed(SecondScreen.route);
Also I can't use Provider, because I don't have access to _CustomNavigatorState class. Could anybody offer me any decision of the problem. Thanks.

I notice you have the nested Scaffolds, it's probably better to move your BottomNavigationBar to the outer Scaffold so you only have one Scaffold in your app. For the body of the outter Scaffold you will have your Stack
Regarding the navigator issues. The body of your app in a Stack with three offstage widgets. Only one of the widgets is visible at a time. When changing between each Offstage widget you don't actually navigate to it, all you have to do is change your _currentScreen to which ever you would like. So if you're on page one and would like to "push" to page 2 then have something like
onPressed: () {
SetState(() {
_currentScreen = FirstScreen.route;
}
}
Then when your body rebuilds from the setState it will set the FirstScreen to be onstage and all other screens to be offstage. Showing you the FirstScreen.

Related

Provider's watch() dosen't catch changing state in flutter

I am studying, provider in flutter. I try to make login process by using beamer and provider.
If user's auth state that is dectected by Provider context.watch<AuthenticationNotifier>().isAuthenticated; is false, BeamGuard force user to go auth screen.
final _routerDelegate = BeamerDelegate(
guards: [
BeamGuard(
pathPatterns: ['/'],
check: (context, location) {
return context.watch<AuthenticationNotifier>().isAuthenticated;
},
beamToNamed: (origin, target) => '/auth',
)
],
locationBuilder: BeamerLocationBuilder(
beamLocations: [PostListLocations(), AuthLocations()]),
);
User click login button in auth screen, auth state change true. I checked user'auth state is changed in AuthScreen.
void attemptVerify(BuildContext context) {
var authNotifier = context.read<AuthenticationNotifier>();
authNotifier.setUserAuth(true);
logger.d(authNotifier.userState);
}
}
but provider in BeamGuard is not watch state change. user do not go to main page, stay in auth page. if i set user's auth state True, user go to directly main page.So I think beamer is not problem. I think Provider doesn't work. I cannot find my mistake. could you help me?
this is full code.
main.dart
final _routerDelegate = BeamerDelegate(
guards: [
BeamGuard(
pathPatterns: ['/'],
check: (context, location) {
return context.watch<AuthenticationNotifier>().isAuthenticated;
},
beamToNamed: (origin, target) => '/auth',
)
],
locationBuilder: BeamerLocationBuilder(
beamLocations: [PostListLocations(), AuthLocations()]),
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812),
builder: (BuildContext context, Widget? child) {
return ChangeNotifierProvider<AuthenticationNotifier>( //provider
create: (context) => AuthenticationNotifier(),
child: MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _routerDelegate,
),
);
},
);
}
}
auth_notifier.dart
import 'package:flutter/widgets.dart';
class AuthenticationNotifier extends ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
void setUserAuth(bool authState) {
_isAuthenticated = authState;
notifyListeners();
}
}
auth_scree.dart
class AuthScreen extends StatefulWidget {
const AuthScreen({Key? key}) : super(key: key);
#override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Scaffold(
body: SingleChildScrollView(
child: ElevatedButton(
onPressed: () {
attemptVerify(context);
},
child: Text("button"),
),
)),
),
);
}
void attemptVerify(BuildContext context) {
var authNotifier = context.read<AuthenticationNotifier>();
authNotifier.setUserAuth(true);
}
}

Flutter: Error says - Could not find a generator for route RouteSettings while trying to navigate to another screen

Although questions with such error messages exist in this site, none solves my problem.
I have a button and on clicking the button, I just need to go to a different screen. But when ever I tap on the screen, the error shows up.
I first setup a route in MaterialApp and then tried to navigate to that route on tapping the button. The full code and the error message are given below:
Code:
import 'livesession1to1.dart';
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(
home: CountDownTimer(),
navigatorKey: NavigationService.navigatorKey, // set property// Added by me later from prev project
// initialRoute: "/",
routes: <String, WidgetBuilder> {
'/liveSession1to1': (context) =>LiveSession1to1(),
},
)
);
}// end of main
class CountDownTimer extends StatefulWidget {
const CountDownTimer();
final String? title='';
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer> {
#override
void initState() {
super.initState();
}// end of initstate
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Live Session'),
),
body: Text('Demo Text'),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_button(title: "Go", onPressed: () =>
Navigator.of(context ,rootNavigator: true).pushNamed('/liveSession1to1', arguments: {'room_found': 123 } )
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
Widget _button({required String title, VoidCallback? onPressed}) {
return Expanded(
child: TextButton(
child: Text(
title,
style: const TextStyle(color: Colors.white),
),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
),
onPressed: onPressed,
));
}
}
Error found:
The following assertion was thrown while handling a gesture:
Could not find a generator for route RouteSettings("/liveSession1to1", {room_found: 123}) in the _WidgetsAppState.
Make sure your root app widget has provided a way to generate
this route.
Generators for routes are searched for in the following order:
For the "/" route, the "home" property, if non-null, is used.
Otherwise, the "routes" table is used, if it has an entry for the route.
Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.
So how to solve the problem ?
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
void main() {
locatorSetup();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final _navService = locator<NavigationHandler>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
onGenerateRoute: generateRoute,
navigatorKey: _navService.navigatorKey,
// I don't know what your first screen is, so I'm assuming it's a Splash Screen
home: SplashScreen());
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
final _navService = locator<NavigationHandler>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
_navService.pushNamed(Routes.LiveSession1to1);
},
child: Text("Go to next page"),
),
));
}
}
class LiveSession1to1 extends StatefulWidget {
const LiveSession1to1({Key? key}) : super(key: key);
#override
State<LiveSession1to1> createState() => _LiveSession1to1State();
}
class _LiveSession1to1State extends State<LiveSession1to1> {
final _navService = locator<NavigationHandler>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
_navService.goBack();
},
child: Text("Go to previous page"),
),
));
}
}
GetIt locator = GetIt.instance;
void locatorSetup() {
locator
.registerLazySingleton<NavigationHandler>(() => NavigationHandlerImpl());
}
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case Routes.LiveSession1to1:
return _getPageRoute(view: LiveSession1to1(), routeName: settings.name);
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
),
);
}
}
PageRoute _getPageRoute({String? routeName, Widget? view}) {
return MaterialPageRoute(
settings: RouteSettings(
name: routeName,
),
builder: (_) => view!,
);
}
class Routes {
static const String LiveSession1to1 = "liveSession1to1";
}
abstract class NavigationHandler {
///Pushes `destinationRoute` route onto the stack
Future<dynamic>? pushNamed(String destinationRoute, {dynamic arg});
///Pushes `destinationRoute` onto stack and removes stack items until
///`lastRoute` is hit
Future<dynamic>? pushNamedAndRemoveUntil(
String destinationRoute, String lastRoute,
{dynamic arg});
///Pushes `destinationRoute` onto stack with replacement
Future<dynamic>? pushReplacementNamed(String destinationRoute, {dynamic arg});
///Pushes `destinationRoute` after popping current route off stack
Future<dynamic>? popAndPushNamed(String destinationRoute, {dynamic arg});
///Pops current route off stack
void goBack();
///Pops routes on stack until `destinationRoute` is hit
void popUntil(String destinationRoute);
///Exits app
void exitApp();
late GlobalKey<NavigatorState> navigatorKey;
}
/// Handles navigation
class NavigationHandlerImpl implements NavigationHandler {
#override
late GlobalKey<NavigatorState> navigatorKey;
/// Constructs a NavigationHandler instance
NavigationHandlerImpl({GlobalKey<NavigatorState>? navigatorKey}) {
this.navigatorKey = navigatorKey ?? GlobalKey<NavigatorState>();
}
NavigatorState? get state => navigatorKey.currentState;
#override
void exitApp() {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
#override
void goBack() {
if (state != null) {
return state!.pop();
}
}
#override
Future? popAndPushNamed(String destinationRoute, {arg}) {
if (state != null) {
return state!.popAndPushNamed(destinationRoute, arguments: arg);
}
}
#override
void popUntil(String destinationRoute) {
if (state != null) {
return state!.popUntil(ModalRoute.withName(destinationRoute));
}
}
#override
Future? pushNamed(String destinationRoute, {arg}) {
if (state != null) {
return state!.pushNamed(destinationRoute, arguments: arg);
}
}
#override
Future? pushNamedAndRemoveUntil(String destinationRoute, String lastRoute,
{arg}) {
if (state != null) {
return state!.pushNamedAndRemoveUntil(
destinationRoute,
ModalRoute.withName(lastRoute),
arguments: arg,
);
}
}
#override
Future? pushReplacementNamed(String destinationRoute, {arg}) {
if (state != null) {
return state!.pushReplacementNamed(destinationRoute, arguments: arg);
}
}
}

Flutter - I can't change the state of a static method of onWillPop

I have a bottomNavigatorBar in my app, and on some screens I don't make it visible, so create a static method to handle it from any class. it works, but when I use the instance of this static method in onWillPop I have the following problems...
E/flutter (10927): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: setState() called in constructor: _telaPrincipalState#cb9c4(lifecycle state: created, no widget, not mounted)
E/flutter (10927): 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
I already used mounted, but it still didn't work
if(!this.mounted){
}
Here is the 3 page code where I use routes
class AtivarEmailPage extends StatefulWidget {
static const String route = "/ativarEmail";
#override
_AtivarEmailPageState createState() => _AtivarEmailPageState();
}
class _AtivarEmailPageState extends State<AtivarEmailPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (!mounted) {
setState(() => telaPrincipal.show());
}
Navigator.of(context).pushNamedAndRemoveUntil(
HomeView.route, (Route<dynamic> route) => false,
arguments: null);
},
child: Scaffold(code...),);
}
}
the problem happens when I call onWillpop to return to HomeView
class HomeView extends StatefulWidget {
static const String route = "/";
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
Widget home(BuildContext context) {
return Scaffold(code..);
}
}
the bottomNavigatorBar manipulated by the static method is in the code below. It is the screen that is under the whole stack, so the navigation bar is floating on top of the other pages
class telaPrincipal extends StatefulWidget {
telaPrincipal({this.categoria, this.exercicio});
static _telaPrincipalState tela = _telaPrincipalState();
#override
_telaPrincipalState createState() {
return tela;
}
static indexBar(int index) {
tela.onItemTapped(index);
}
static void hide() {
tela.hideNavBar();
}
static void show() {
print("show");
tela.showNavBar();
}
}
class _telaPrincipalState extends State<telaPrincipal> {
void hideNavBar() {
setState(() {
_show = false;
_bottomBarHeight = 0;
});
}
void showNavBar() {
setState(() {
_show = true;
_bottomBarHeight = 60;
});
}
void onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
int _selectedIndex = 0;
final navigatorKey = GlobalKey<NavigatorState>();
//bottomNavigatorBar routes
final pagesRouteFactories = {
HomeView.route: () => MaterialPageRoute(builder: (context) => HomeView()),
programaTreino.route: () => MaterialPageRoute(
builder: (context) => programaTreino(
exercicioEscolhido: null,
categoria: null,
)),
pesquisar_view.route: () =>
MaterialPageRoute(builder: (context) => pesquisar_view()),
};
bool _show = true;
double _bottomBarHeight = 60;
#override
Widget build(BuildContext context) {
return WillPopScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: HexColor("#121212"),
body: _buildBody(),
bottomNavigationBar: Container(
height: _bottomBarHeight,
width: MediaQuery.of(context).size.width,
child: _show
? _buildBottomNavigationBar(context)
: Container(
color: Colors.white,
width: MediaQuery.of(context).size.width,
),
)),
),
onWillPop: () async {
navigatorKey.currentState.maybePop();
return false;
},
);
}
Widget _buildBody() => MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: navigatorKey,
onGenerateRoute: (settings) {
String routeName = settings.name;
//Map<String, dynamic> args = route.arguments; // Get any arguments passed to the route
print("ongenate$routeName}");
switch (routeName) {
case AtivarEmailPage.route:
return MaterialPageRoute(builder: (context) => AtivarEmailPage());
break;
default:
return pagesRouteFactories[settings.name]();
break;
}
});
Widget _buildBottomNavigationBar(context) => BottomNavigationBar(
currentIndex: _selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home_filled),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.fitness_center),
label: 'Treinos',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Pesquisar',
),
],
onTap: (routeName) {
print(pagesRouteFactories.keys.toList()[routeName]);
navigatorKey.currentState.pushNamed(
pagesRouteFactories.keys.toList()[routeName],
arguments: routeName == 0 ? null : [null, null]);
onItemTapped(routeName);
},
backgroundColor: HexColor("#FFFFFF").withOpacity(0.08),
selectedItemColor: HexColor("#FFCC80"),
unselectedItemColor: HexColor("#FFFFFF").withOpacity(0.30),
);
}
screens:
HomeView
AtivarEmail
I would like to go back with the bottomNavigatorBar visible to Homeview after pressing the android back button on the ActivateEmail screen
so create a static method to handle it from any class.
The short answer is: don't do that.
Read up on flutter state management. Pick the one you like best, but don't home-brew a solution.
It looks like you are using mounted in reverse. mounted is true when the widget is part of the tree. See The API documentation
Try
if (mounted) {
setState(() => telaPrincipal.show());
}
instead of if (!mounted)

Flutter app: hide appBar action according to ApplicationState value

I'm quite new to Flutter and I think I haven't understand all the logic behind the state management with Providers.
I've the following widget:
class App extends StatelessWidget {
List<IconButton> navigationActions(BuildContext context) {
return
Consumer<ApplicationState>(builder: (context, appState, _) {
if (appState.loginState == 'loggedIn') {
return [IconButton(
icon: const Icon(Icons.logout),
tooltip: 'Logout',
onPressed: () {
context.read<ApplicationState>().signOut();
},
)];
}
})
;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FOO'),
actions: navigationActions(context)
),
body: ListView(
.........
)
)
}
And I want to show/hide the AppBar action according to the flag loginState set inside ApplicationState
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
}
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
String _loginState = 'loggedOut';
String get loginState => _loginState;
}
I'm not sure about how to implement the function navigationActions.
Which should be the return type? Since I'm not returning a data in the else branch I'm not sure about how to manage that type.
Maybe there are smarter solution, I don't know yet.. Someone has ever implemented a similar logic with Providers?
navigationActions has to return List<IconButton>, but you are returning the result of Consumer which is a Widget. You can use other methods to get the ApplicationState. Here is example code which does what you want:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ApplicationState extends ChangeNotifier {
String _loginState = 'loggedIn';
set loginState(String state) {
_loginState = state;
}
get loginState => _loginState;
void toggleState() {
if (loginState == 'loggedIn')
loginState = 'loggedOut';
else
loginState = 'loggedIn';
notifyListeners();
}
void signOut() {}
}
class ActionTest extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ApplicationState(),
child: ActionApp(),
);
}
}
class ActionApp extends StatelessWidget {
List<IconButton> navigationActions(BuildContext context) {
final appState = Provider.of<ApplicationState>(context);
if (appState.loginState == 'loggedIn') {
return [
IconButton(
icon: const Icon(Icons.logout),
tooltip: 'Logout',
onPressed: () {
appState.signOut();
},
)
];
} else {
return [];
}
}
#override
Widget build(BuildContext context) {
final appState = Provider.of<ApplicationState>(context);
return Scaffold(
appBar: AppBar(title: Text('FOO'), actions: navigationActions(context)),
body: Container(),
floatingActionButton: FloatingActionButton(
onPressed: () {
appState.toggleState();
},
child: Icon(appState.loginState == 'loggedIn'
? Icons.toggle_off
: Icons.toggle_on),
),
);
}
}

Flutter Bottom Navigation Bar - Best Practice Question

I am pretty new to Flutter. I want to develop a Application with a Bottom Navigation Bar. But I don't really know what is the best way of Navigation. I made a custom Version of this Tutorial:
https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386
What I did until now was:
Created a Main Widget with a Scaffold with a BottomNavigationBar
Everytime the index is changing I change the child Property of the Scaffold
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainWidget()
);
}
}
/// This is the stateful widget that the main application instantiates.
class MainWidget extends StatefulWidget {
#override
_MainWidgetState createState() => _MainWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MainWidgetState extends State<MainWidget> {
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
index: _selectedIndex,
children: allDestinations.map<Widget>((Destination destination) {
return DestinationView(destination: destination);
}).toList(),
)
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.blue,
currentIndex: _selectedIndex,
onTap: (int index) {
setState(() {
_selectedIndex = index;
});
},
items: allDestinations.map((Destination destination) {
return BottomNavigationBarItem(
icon: Icon(destination.icon),
label: destination.title
);
}).toList(),
)
);
}
}
enum DestinationType {
Page1,
Page2,
Page3,
Page4,
Page5
}
class Destination {
const Destination(this.type, this.title, this.icon);
final DestinationType type;
final String title;
final IconData icon;
}
const List<Destination> allDestinations = <Destination>[
Destination(DestinationType.Page1, 'Page1', Icons.lightbulb),
Destination(DestinationType.Page2, 'Page2', Icons.search),
Destination(DestinationType.Page3, 'Page3', Icons.attach_money),
Destination(DestinationType.Page4, 'Page4', Icons.calendar_today_outlined),
Destination(DestinationType.Page5, 'Page5', Icons.settings)
];
I return a DestinationView where I am checking what Destination should be built.
class DestinationView extends StatefulWidget {
const DestinationView({Key key, this.destination}) : super(key: key);
final Destination destination;
#override
_DestinationViewState createState() => _DestinationViewState();
}
class _DestinationViewState extends State<DestinationView> {
#override
Widget build(BuildContext context) {
switch (widget.destination.type) {
case DestinationType.Page1:
return Page1Destination(destination: widget.destination);
case DestinationType.Page2:
return Page2Destination(destination: widget.destination);
case DestinationType.Page3:
return Page3();
case DestinationType.Page4:
return Page4();
case DestinationType.Page5:
return Page5();
}
}
}
If I have a Part where I want to have Navigation in only this Part, I create a Navigator and define routes:
class Page1Destination extends StatefulWidget {
const Page1Destination({Key key, this.destination}) : super(key: key);
final Destination destination;
#override
_Page1DestinationState createState() => _Page1DestinationState();
}
class _Page1DestinationState extends State<Page1Destination> {
#override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
settings: settings,
builder: (BuildContext context) {
switch(settings.name) {
case '/':
return Page1Home(destination: widget.destination);
case '/list':
return Page1List();
case '/settings':
return Page1Settings(destination: widget.destination);
}
},
);
},
);
}
}
Inside these widgets I use Navigator.pushNamed and so on.
If the Tab/Page is only one widget. I only return a normal widget without any routes.
But if want to call a Widget in Page 1 with the route /list and a parameter from another page. I don't know how to do that.
I'm pretty sure there is a better way of handling that kind of Navigation.
So maybe one of you knows how I can create a better Navigation-Handler.