I'm experiencing a really strange problem with Flutter's navigation.
I'm using named routes. For this reason I'm supplying my Navigator with an onGenerateRoute method:
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
onGenerateRoute: Routes.onGenerateRoute,
initialRoute: Routes.ROOT,
);
here's my onGenerateRoute:
static Route onGenerateRoute(RouteSettings settings) {
switch (settings.name) {
case ROOT:
return MaterialPageRoute(
builder: (BuildContext context) {
return MyRootPage();
},
);
case PAGE_1:
return MaterialPageRoute(
builder: (context) {
return MyPage1();
},
fullscreenDialog: true,
);
case PAGE_2:
return MaterialPageRoute(
builder: (context) {
return MyPage2();
}
);
default:
break;
}
return null;
}
Now, suppose I start my application landing on the ROOT route, and then I navigate to ´PAGE_1´ by using
Navigator.of(context).pushNamed(Routes.PAGE_1)
Then I navigate to MyPage2 by using:
`Navigator.of(context).pushNamed(Routes.PAGE_2)`.
Here's the problem.
I'm expecting MyPage2 to be pushed onto MyPage1, resulting in this widget tree:
MaterialApp
|__MyRootPage()
|__MyPage1()
|__MyPage2()
but instead, I get this:
MaterialApp
|__MyPage2()
|__MyRootPage()
|__MyPage1()
What am I missing?
Thanks in advance!
Related
I am using the builder function of my MaterialApp to wrap my routes with a widget.
But I only want to show the wrapper on specific routes.
Any idea how to achieve this?
Actually I am using GetX and GetMaterialApp, but I don't think that it makes any difference.
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: HomeScreen(),
builder: (context, child) {
//Only show GlobalPlayerWrapper on specific routes
return GlobalPlayerWrapper(child: child!);
},
title: 'Aschaffenburg',
);
}
You coul use MaterialApp's onGenerateRoute callback to generate different results depending on the route:
return MaterialApp(
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case'/':
return MaterialPageRoute(builder: (_) => HomeScreen());
case '/otherPage':
/* return other page route with your wrapper*/
}
},
initialRoute: MateriealPageRoute(builder:(_) => HomeScreen()),
);
I am new to flutter and trying to get this to work. I have a Welcome() page, which has two buttons (SignUp and SignIn), which take you to SignUp() and SignIn() pages. After successfully authenticating, the app should navigate to Home(), but it does not. Neither does it go back to Welcome() page if user logs out. Also, if the app is started and user was already logged in, it doesn't automatically go to Home(), it stays at Welcome(). What am I doing wrong here?
I am using a StreamProvider like so
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<User>.value(
value: AuthService().user, child: Wrapper());
}
And my wrapper looks like this:
class Wrapper extends StatelessWidget {
const Wrapper({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user != null)
print(user.id);
else
print("User is null");
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => user == null ? Welcome() : Home(),
'/signup': (context) => SignUp(),
'/signin': (context) => SignIn()
'/profile': (context) => Profile(),
'/edit': (context) => Edit()
});
}
This prints the user ID, which means the user is not null, but it does not navigate to Home().
I'm not dictating you what to do but i will just show you how i usually do.
The concept is simple. When the user successfull login or signup, you must save this information in a shared_preference. It may be like auth : true.
You must also wait on the app launching process to see if the user already login or not and based on that, you can navigate to another screen.
To check at startup if user is authenticated, you must read the auth property you previously added in shared_preference. Look at this code :
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
This code is so self explanatory. You can adapt it to your edge.
To navigate to a new screen it is simple, just use this code :
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
I am learning about integration testing in flutter and I'm running into a problem.
My app contains a sign-in button widget and my test starts with pumping it.
However, I can't seem to target it.
Every attempt results in the following error:
integration_test/doctor_integration_test.dart:21:24: Error: Getter not found: 'SignInPage'.
expect(find.byType(SignInPage), findsOneWidget);
^^^^^^^^^^
FAILURE: Build failed with an exception.
The SignInPage is called within:
MaterialApp _buildMaterialApp(BuildContext context) {
return MaterialApp(
title: 'Skinopathy: Doctor',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
routes: {
'/': (context) {
return BlocListener<AppBloc, AppState>(
listener: (context, state) {
if (state is AppAuthenticated) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => WelcomePage(),
),
);
} else if (state is AppUnauthenticated) {
Navigator.pushReplacementNamed(context, '/sign_in');
} else if (state is AppOutdated) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => OutdatedVersionPage(),
),
);
}
},
child: Center(
child: SplashPage(),
),
);
},
'/sign_in': (context) {
return SignInPage();
},
My presumption is that the integration takes place before the SignInPage is loaded.
_buildMaterialApp is also a child of Widget _buildRootLevelWidgets(BuildContext context)
How do I properly target SignInPage for testing?
Is there anything else I'm doing wrong?
Please note: I did NOT build this app; I'm just here to test it.
Thanks in advance!
And I'm dumb; I forgot to import the corresponding file that allowed it recognize SignInPage
I've looked over several other posts with this same error about the Navigator and either their code looks different, it fails in totally different places, or other reasons and I must be missing something important. Where this fails for me is only from resuming from background or sleep. The app lifecycle detects "resume" and I want to navigate to the login page for the user to select a profile or login. The error below shows any way I try to use a Navigator in that function didChangeAppLifecycleState(AppLifecycleState state). Actually if I use Navigator anywhere in main.dart it gives the error. Outside of main.dart Navigator works great.
Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
The code that causes the error in main.dart :
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print("State changed! ${state}");
setState(() {
_notification = state;
});
if(state == AppLifecycleState.resumed){
NavService().navigateTo(context, '/login');
}
}
The main.dart build looks like this:
#override
Widget build(BuildContext context) {
return
MaterialApp(
theme: new ThemeData(
primarySwatch: themeSwatchColor,
brightness: Brightness.light,
primaryColor: themePrimaryColor,
accentColor: themeAccentColor,
),
initialRoute: '/',
navigatorObservers: <NavigatorObserver>[
NavService(), // this will listen all changes
],
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case '/':
return MaterialPageRoute(builder: (_) => LoginPage());
case '/login':
return MaterialPageRoute(builder: (_) => LoginPage());
case '/home':
return MaterialPageRoute(builder: (_) => HomePage());
case '/items':
return MaterialPageRoute(builder: (_) => ItemLookupPage());
case '/settings':
return MaterialPageRoute(builder: (_) => SettingsPage());
case '/oldsettings':
return MaterialPageRoute(builder: (_) => SecondPage());
case '/pickorders':
return MaterialPageRoute(builder: (_) => ReceivedOrdersPage());
case '/orders':
return MaterialPageRoute(builder: (_) => OrdersPage());
case '/receiving':
return MaterialPageRoute(builder: (_) => ReceivingPage());
case '/inventory':
return MaterialPageRoute(builder: (_) => InventoryPage());
default:
return MaterialPageRoute(builder: (_) => LoginPage());
}
},
home: (noAccount == true)
? LoginPage()
: HomePage(),
);
}
NavService.dart:
class NavService extends RouteObserver {
void saveLastRoute(String lastRoute) async {
if(lastRoute != "/login" && lastRoute != "/error"){
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('last_route', lastRoute);
}
}
Future<dynamic> navigateTo(BuildContext context, String routeName, {Map data}) async {
saveLastRoute(routeName);
return Navigator.pushNamed(context, routeName, arguments: data);
}
}
I also tried skipping my NavService and used Navigator directly, but the same error shows.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LoginPage(),
),
);
I tried using a GlobalKey as other posts have suggested, but the NavService() using the RouteObserver breaks when I do that.
The NavService and page routing works very well anywhere in the app. Its only while navigating in main.dart I'm having the issue. I just noticed if I place the above Navigator.of().push in initState() I get the same error. Maybe my MaterialApp is setup wrong? Or am I using the NavService incorrectly?
Thanks for any help!
The didChangeAppLifecycleState method does not provide any context unlike the build method. You would have to navigate without using context by setting a global key for your navigation:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Pass it to MaterialApp:
MaterialApp(
title: 'MyApp',
onGenerateRoute: generateRoute,
navigatorKey: navigatorKey,
);
Push routes:
navigatorKey.currentState.pushNamed('/someRoute');
Credits to this answer
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;
}
}
);
}