I wonder how we can call generateRoute without arguments
onGenerateRoute: RouteGenerator.generateRoute
my expectation would be:
RouteGenerator.generateRoute(settings)
Thanks,
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: NumberCreator().stream,
builder: (context, snapshot) {
print('StreamBuilder: ${snapshot.connectionState}');
return MaterialApp(
title: 'Demo',
**onGenerateRoute: RouteGenerator.generateRoute,**
onGenerateInitialRoutes: (String initialRouteName) {
return [
RouteGenerator.generateRoute(
RouteSettings(name: '/', arguments: snapshot)),
];
},
);
});
}
}
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case '/':
if (args is AsyncSnapshot<int>) {
It's not really that you are calling onGenerateRoute without arguments, it is because you are passing a parameters which comply to the expected type RouteFactory defined as follow in the flutter documentation:
typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings);
And your generateRoute comply to this type as it is a function which takes a RouteSettings parameter and returning an object of type Route<dynamic>.
Here is the code I've used in my projects:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
initialRoute: /*<my_initial_route */,
onGenerateRoute: MyRouter.generateRoute,
);
}
}
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
// your cases ...
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
),
);
}
}
}
Related
I am making an app that has a persistent BottomNavigationBar. Depending on user != null the user gets into the LoginScreen or HomeScreen
There are two ways to get this done by routing:
First approach: include LoginScreen inside the nested Navigator from the MainScaffold
the mainScaffold the user gets redirected from, will have a body that has its own Navigator class inside that body.
App
class AppName extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainScaffold(),
);
}
}
RouteGenerator
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
dynamic builder;
switch (settings.name) {
case '/':
builder = LoginScreen();
break;
case '/mainScaffold':
builder = MainScaffoldBody();
break;
case '/mainScaffold/page2':
builder = (BuildContext context) => Page2();
break;
...
default:
return _errorRoute();
}
return PageRouteBuilder(
...
pageBuilder: (BuildContext context, _animation, __animation) => builder,
...
);
}
MainScaffold
class MainScaffold extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBarSelector(),
body: Navigator(
key: _navigatorKey,
initialRoute: '/mainScaffold/home',
onGenerateRoute: RouteGenerator.generateRoute
},
),
bottomNavigationBar: CustomBottomNavigationBar(controller: _controller),
);
}
}
Alternative: don't include LoginScreen inside the nested Navigator from the MainScaffold but seperate it
The alternative approach would be to have MainScaffold as initialRoute and LoginScreen inside the bodyof the MainScaffold.
If I want the BottomNavigation to dissapear on for example LoginScreen, I could simply toggle the sized/visibility of that bar. The RouteGenerator would look like such.
App
class AppName extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute, // this static creates possibility of using parameters
);
}
}
RouteGenerator
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
dynamic builder;
switch (settings.name) {
case '/':
builder = MainScaffold();
break;
case '/login':
builder = LoginScreen();
break;
case '/mainScaffold/home':
builder = HomeScreenArguments();
break;
...
default:
return _errorRoute();
}
return PageRouteBuilder(
...
pageBuilder: (BuildContext context, _animation, __animation) => builder,
...
);
}
So what is the best approach here:
LoginScreen is part of the nested Navigator from MainScaffold
LoginScreen is not part of the nested Navigator from MainScaffold
I am trying to implement a simple navigation logger in my flutter app. Created
a new class extending RouteObserver:
class AuthMiddleware extends RouteObserver {
AuthMiddleware();
#override
void didPush(Route route, Route? previousRoute) {
print(
"PUSH FROM ${previousRoute.settings.name} TO ${route.settings.name} ");
super.didPush(route, previousRoute);
}
#override
void didPop(Route route, Route? previousRoute) {
print(
"POP FROM ${previousRoute.settings.name} TO ${route.settings.name}");
super.didPop(route, previousRoute);
}
}
And then assigned it to MaterialApp:
MaterialApp(
navigatorKey: rootNavigatorKey,
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.light,
theme: THEME_DATA,
onGenerateRoute: (RouteSettings settings) {
String? routeName = settings.name;
Widget getPage() {
switch (routeName) {
case "/about":
return AboutPage();
default:
return HomePage();
}
}
return MaterialPageRoute(builder: (context) => getPage());
},
navigatorObservers: [AuthMiddleware()], //***Set created observer here***
),
I was expecting to see that it will print something like
PUSH FROM / TO /about
But I can only see:
PUSH FROM null TO null
What I am doing wrong?
you need to pass settings as a second parameter to MaterialPageRoute
return MaterialPageRoute(builder: (context) => getPage(), settings: settings);
What's the correct way of setting up navigation architecture named routes while using a bottomNavigationBar?
Here's my current setup but I feel there's a better way of doing it:
main.dart:
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) {
switch (settings.name) {
case NamedRoutes.splashScreen:
return SplashScreen();
case NamedRoutes.login:
return LoginPage();
case NamedRoutes.mainApp:
return NavigatorSetup();
default:
throw Exception('Invalid route: ${settings.name}');
}
});
navigatorSetup.dart:
IndexedStack(
index: Provider.of<RoutesProvider>(context).selectedViewIndex,
children: [FirstMain(), SecondMain(), ThirdMain(), FourthMain()],
), bottomNavigationBar...
in each main files there is the following setup
class FirstMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Navigator(
key: Provider.of<RoutesProvider>(context).homeKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) {
switch (settings.name) {
case '/':
case NamedRoutes.mainPage:
return MainPage();
case NamedRoutes.singleMainPage:
return SingleMainPage();
default:
throw Exception('Invalid route: ${settings.name}');
}
},
);
},
);
}
}
Then my routes provider looks like this:
class RoutesProvider extends ChangeNotifier {
int _selectedViewIndex = 0;
get selectedViewIndex => _selectedViewIndex;
set selectedViewIndex(int newIndex) {
_selectedViewIndex = newIndex;
notifyListeners();
}
GlobalKey _mainKey = GlobalKey<NavigatorState>();
GlobalKey _homeKey = GlobalKey();
GlobalKey _secondKey = GlobalKey();
GlobalKey _thirdKey = GlobalKey();
GlobalKey _fourthKey = GlobalKey();
get mainKey => _mainKey;
get homeKey => _homeKey;
get secondKey => _secondKey;
get thirdKey => _thirdKey;
get fourthKey => _fourthKey;
}
The way I'm currently changing routes when on another page of the indexedStack
final RoutesProvider routesProvider = Provider.of<RoutesProvider>(context, listen: false);
final GlobalKey thirdKey = routesProvider.thirdKey;
routesProvider.selectedViewIndex = 2;
Navigator.pushReplacementNamed(thirdKey.currentContext, NamedRoutes.third);
The better way to navigate
Creating a route_generator
import 'package:flutter/material.dart';
import 'package:routing_prep/main.dart';
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
// Getting arguments passed in while calling Navigator.pushNamed
final args = settings.arguments;
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => FirstPage());
case SecondPage.routeName:
// Validation of correct data type
if (args is String) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
}
// If args is not of the correct type, return an error page.
// You can also throw an exception while in development.
return _errorRoute();
default:
// If there is no such named route in the switch statement, e.g. /third
return _errorRoute();
}
}
static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('ERROR'),
),
);
});
}
}
As you can see, you've moved from having bits of routing logic everywhere around your codebase, to a single place for this logic - in the RouteGenerator. Now, the only navigation code which will remain in your widgets will be the one pushing named routes with a navigator.
Before you can run and test the app, there's still a bit of a setup to do for this RouteGenerator to function.
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
...
// Initially display FirstPage
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
...
RaisedButton(
child: Text('Go to second'),
onPressed: () {
// Pushing a named route
Navigator.of(context).pushNamed(
SecondPage.routeName,
arguments: 'Hello there from the first page!',
);
},
)
...
}
}
class SecondPage extends StatelessWidget {
static const routeName = "/second";
// This is a String for the sake of an example.
// You can use any type you want.
final String data;
SecondPage({
Key key,
#required this.data,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Routing App'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Second Page',
style: TextStyle(fontSize: 50),
),
Text(
data,
style: TextStyle(fontSize: 20),
),
],
),
),
);
}
}
I am trying to learn Bloc with creating dynamic simple theme manager. I create a class called theme_bloc :
class DefaultApi {
final String name;
final ThemeData theme;
DefaultApi(this.name, this.theme);
}
class ThemeBloc {
DefaultApi _defualt;
ThemeBloc() {}
final _themeManager = StreamController<DefaultApi>.broadcast();
Stream<DefaultApi> get themeManager => _themeManager.stream;
Function(DefaultApi) get changeTheme => _themeManager.sink.add;
DefaultApi initialTheme() {
_defualt = DefaultApi("light", ThemeManager.instance.lightTheme);
return _defualt;
}
void dispose() {
_themeManager.close();
}
}
to inject bloc class i use provider like this:
class ThemeProvider with ChangeNotifier{
ThemeBloc _bloc;
ThemeProvider(){
_bloc = ThemeBloc();
}
ThemeBloc get bloc => _bloc;
}
I use StringBuilder in main class to set theme like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: ThemeProvider(),
child: Consumer<ThemeProvider>(
builder: (crx, provider, child) {
return StreamBuilder(
initialData: provider.bloc.initialTheme(),
stream: provider.bloc.themeManager,
builder: (context, AsyncSnapshot<DefaultApi>snapshot) {
return snapshot.hasData? MaterialApp(
title: 'Flutter Demo',
theme: snapshot.data.theme,
home: HomePage(),
):Container();
});
},
),
);
}
In HomePage page i have switch to change theme between light and dark theme.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = Provider.of<ThemeProvider>(context).bloc;
return Scaffold(
appBar: AppBar(
title: Text("Theme manager"),
),
body: StreamBuilder<DefaultApi>(
stream: bloc.themeManager,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Switch(
value: true,
onChanged: (bool value) {
},
);
} else if (!snapshot.hasData) {
return Text("loading");
}
return Text("!!!!");
}),
);
}
But after running just loading is printed into screen.
Someone know what is my problem?
Your problem will solve if the StreamBuilder widget inside HomePage have initialData. like this:
...
body: StreamBuilder<DefaultApi>(
initialData: bloc.initialTheme(), // <<< new line
stream: bloc.themeManager,
builder: (context, snapshot) {
...
This input is not required. Yet I don't have any clue why it's absence cause problem here.
There are few deeper considerations:
1. Using ChangeNotifierProvider
As the documentation recommends, use ChangeNotifierProvider instead of ChangeNotifierProvider.value. Obviously because you're creating a new instance
...
return ChangeNotifierProvider(
create: (_) => ThemeProvider(),
child: Consumer<ThemeProvider>(
...
2. Prevent useless listening
Based on this guide, if you are using provider to only call actions, you have to use listen: false to prevent useless rebuilds.
Is there some chance to get current context on the main.dart? I am using the sharing intent for listening applinks, and I need to redirect to specify page. But I don't know how can I use the context.
#override
Widget build(BuildContext context) {
ReceiveSharingIntent.getInitialText().then((String val){
//some logic
Navigator.pushNamed(context, ....);
});
return MaterialApp(
title: 'MyApp',
initialRoute: Routes.home,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case Routes.home:
return SimplePageRoute(builder: (context) => HomeScreen());
break;
}
}
);
}
I have got this error
Unhandled Exception: Navigator operation requested with a context that
does not include a Navigator.
Ok I am understand, but how can I get the current context in this place?
Ok I fixed it with navigatorKey!!
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
ReceiveSharingIntent.getInitialText().then((String val){
//some logic
navigatorKey.currentState.pushNamed(Routes.myPage);
});
return MaterialApp(
title: 'MyApp',
initialRoute: Routes.home,
navigatorKey: navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case Routes.home:
return SimplePageRoute(builder: (context) => HomeScreen());
break;
}
}
);
}