Is it possible to check our previous navigate pages on Flutter? - flutter

How to check previous navigate pages?
Example: HomeScreen > ProductListScreen >ViewCart > Payment (Now I am in the Payment Page)
like this: print(priousPages);

Pass previous page widget as parameter to the Payment widget constructor.
// In page you want to navigate to Payment widget.
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Payment(priousPages: this.runtimeType);
));
Or, you can use NavigatorObserver to receive events of navigator, and you can record changes of route in observer.
class _NavigatorHistory extends NavigatorObserver {
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
print("${route.settings.name} pushed");
}
#override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
print("${route.settings.name} popped");
}
#override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
print("${oldRoute.settings.name} is replaced by ${newRoute.settings.name}");
}
#override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
print("${route.settings.name} removed");
}
}
You can use route.settings.name to identify the route. So when you create route, you should give a route name.
MaterialPageRoute(
builder: (context) => YourWidget(),
settings: RouteSettings(name: "/payment")
)
Lastly, don't forget to register this observer to App.
MaterialApp(
navigatorObservers:[ _NavigatorHistory()],
title: "App title",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: homePage,
)

Related

flutter route settings name is null

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);

Flutter Provider and Navigator

so This is my first try with Flutter. I come from Angular where dependency injection made routing a breeze. Just injecting the Router to any service would just do the trick.
Here is my scenario for flutter app:
In '/login' page I click Login button. This calls method from Auth provider, where after validating credentials Application should be routed to '/home'.
It's just something I can't wrap my head around even though I read all possible threads in the freaking internet. Everybody said one shoud wrap MaterialApp with ChangeNotifierProvider which I did - what am I doing wrong?
Here is my code. Can you point me to the right direction?
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => Auth(context: context),
child: MaterialApp(
initialRoute: "/login",
routes: {
"/login": (context) => LoginPage(),
"/home": (context) => HomePage()
},
),
);
}
}
//This is my Login page Widget - After successful login I want to be able to use Navigator to push to '/home' route
class LoginPage extends StatelessWidget {
#override
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Login"),
),
body: ElevatedButton(
//
onPressed: () => {
// here I want to use login method on Auth provider. Which should validate credentials and if valid redirect to home page
Provider.of<Auth>(context, listen: false).login()
},
child: Text("login"),
));
}
}
//This is my Home page Widget
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(appBar: AppBar(title: Text("Home"))),
);
}
}
//This is my auth controller where After successful login I want to redirect to HomePage
class Auth extends ChangeNotifier {
BuildContext context;
Auth({
required this.context,
});
// This is login method of Auth provider -
login() {
//Here after validating the credentials (eg user / password) I want to redirect to '/home' route.
Navigator.of(context).pushNamed("/home");
}
}
I have a better solution to change your pages route based on authentication status, do code like this in your route table in MaterialApp:
route:{
'/': (context) => Consumer<AuthProvider>(
builder: (context, value, child) => value.isAuth()
? yourMainPageAfterAuth()
: AuthScreen()}
single slash will be considered as your home page, so validate your authentication in your auth provider then route to your home page after authentication
Navigator.of(context).pushNamed('/');

flutter-web - Avoid initialRoute from initiating when the app launched with a different route via the browser's address bar?

New to Flutter.
I'm making an app that has a splash screen that initially shows up when the user opens the app. After 3 seconds, the app will show the login or the dashboard screen, depending on the authentication state.
Here's my code.
main.dart
void main() {
runApp(myApp);
}
MaterialApp myApp = MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => SplashScreen(),
"/signin": (context) => SignInScreen(),
"/notes": (context) => NotesScreen(),
},
);
splash_screen.dart
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_goToNextScreen();
}
void _goToNextScreen() {
Future.delayed(
Duration(seconds:3),
() async {
AuthState authState = await Auth.getAuthState();
String route = authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
Navigator.pushReplacementNamed(context, route);
}
);
}
// build() override goes here...
}
I've been debugging the app with a web-server. When the app launches with the url localhost:8000/, everything seems fine. However, if the app started with the url localhost:8000/notes, the splash screen, I think, still gets initiated. What happens is the app will show the notes screen, then after 3 seconds, the app will open another notes screen.
Any ideas?
Because first render always started at root '/', it's preferable to use your own path for splash screen, like
initialRoute: '/splash'.
To hide this path in the address bar, replace routes map with route generator:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (RouteSettings settings) {
// print current route for clarity.
print('>>> ${settings.name} <<<');
switch (settings.name) {
case '/splash':
return MaterialPageRoute(
builder: (context) => SplashScreen(),
// settings omitted to hide route name
);
case '/signin':
return MaterialPageRoute(
builder: (context) => SignInScreen(),
settings: settings,
);
case '/notes':
return MaterialPageRoute(
builder: (context) => NotesScreen(),
settings: settings,
);
case '/':
// don't generate route on start-up
return null;
default:
return MaterialPageRoute(
builder: (context) => FallbackScreen(),
);
}
},
initialRoute: '/splash',
);
}
}
See since the main logic is we cannot have await in the init state so the page will build irrespective of the any logic you provide. I have a solution to this, there may be some advance or other good solutions too, so this is what I would use.
I would use a concept of future builder. What it will do is wait for my server and then build the whole app.
So process is
In your main.dart
use
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
//await for my server code and according to the variable I get I will take action
//I would have a global parameter lets say int InternetOff
await checkServer();
runApp(MyApp());
} catch (error) {
print(error);
print('Locator setup has failed');
//I can handle the error here
}
}
Now MyApp stateless Widget that will help us choose our path
class MyApp extends Stateless Widget{
Widget build(BuildContext context) {
//Using this FutureBuilder
return FutureBuilder<String>(
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// AsyncSnapshot<Your object type>
// Now if InternetOff is equal to one I would make it go to home
if(InternetOff==1) return MaterialApp(
theme: ThemeData.light(),
home: CheckInternet(),
debugShowCheckedModeBanner: false,
);
//else go to Home similarly with these if and else you can add more conditions
else {
return MaterialApp(
theme: ThemeData.dark(),
home: UserHome(),
debugShowCheckedModeBanner: false,
);
}
}
}
},
);
}
}
First of all, flutter-web like any other Single Page Application supports hash based routing. As a result if you want to access
localhost:8000/notes
you have to access it as
localhost:8000/#/notes
Cleaner way to handle auth state
Call getAuthState function before runApp() to make sure that the auth state is set before app is initialized. And pass authState to SplashScreen widget as parameter.
void main() {
WidgetsFlutterBinding.ensureInitialized();
AuthState authState = await Auth.getAuthState();
runApp(MaterialApp myApp = MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => SplashScreen(authState: authState),
"/signin": (context) => SignInScreen(),
"/notes": (context) => NotesScreen(),
},
));
}
splash_screen.dart
class SplashScreen extends StatefulWidget {
final AuthState authState;
SplashScreen({Key key, this.authState}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_goToNextScreen();
}
void _goToNextScreen() {
Future.delayed(
Duration(seconds:3),
() async {
String route = widget.authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
Navigator.pushReplacementNamed(context, route);
}
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
And if you want even more cleaner way to handle auth state, you have to use state management solution like Provider.

Detect when we moved back to previous page in Flutter

We moved from Page1 to Page2 but now from Page2 we move back again to Page1 like this:
Navigator.of(context).pop();
How can we detect on Page1 that we went back?
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NextPage(),
),
).then((_){
// Here you will get callback after coming back from NextPage()
// Do your code here
});
In your Page1, When you push Page2 wait for it to pop
Future<void> _goToPage2() async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page2(),
),
);
print("Page2 is popped");
}
Another solution, which is more verbose but also more efficient if you push a lot of Routes from the same Widget, would be to create your own NavigatorObserver.
1- Create your NavigatorObserver
final routeObserver = MyRouteObserver();
class MyRouteObserver extends NavigatorObserver {
final Set<RouteAware> _listeners = <RouteAware>{};
void subscribe(RouteAware routeAware) {
_listeners.add(routeAware);
}
void unsubscribe(RouteAware routeAware) {
_listeners.remove(routeAware);
}
#override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
for (var listener in _listeners) {
listener.didPop();
}
}
}
2- Add it to your MaterialApp
return MaterialApp(
navigatorObservers: [routeObserver],
...
3- Implement RouteAware in the Widget where you want to listen to Navigation events, and subscribe/unsubscribe to your routeObserver
class _TablePageState extends State<TablePage> implements RouteAware {
#override
void initState() {
super.initState();
routeObserver.subscribe(this);
}
#override
Widget build(BuildContext context) {
return Container();
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPop() {
//This method will be called from your observer
}
#override
void didPopNext() {}
#override
void didPush() {}
#override
void didPushNext() {}
}

How to remove the first screen from route in Flutter?

I am creating a loading screen for an app. This loading screen is the first screen to be shown to the user. After 3 seconds the page will navigate to the HomePage. everything is working fine. But when the user taps back button the loading screen will be shown again.
FIRST PAGE CODE
import 'dart:async';
import 'package:flutter/material.dart';
import 'home_page.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Future.delayed(
Duration(
seconds: 3,
), () {
// Navigator.of(context).pop(); // THIS IS NOT WORKING
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlutterLogo(
size: 400,
),
),
);
}
}
HOMEPAGE CODE
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text('HomePage'),
),
),
);
}
}
I tried to add Navigator.of(context).pop(); before calling the HomePage but that is not working. This will show a blank black screen.
Any ideas??
You need to use pushReplacement rather than just push method. You can read about it from here: https://docs.flutter.io/flutter/widgets/Navigator/pushReplacement.html
And to solve your problem just do as explain below.
Simply replace your this code:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
with this:
Navigator. pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
Yes, I found the same problem as you. The problem with replace is that it only works once, but I don't know why it doesn't work as it should. For this after a few attempts, I read the official guide and this method exists: pushAndRemoveUntil (). In fact, push on another widget and at the same time remove all the widgets behind, including the current one. You must only create a one Class to management your root atrough the string. This is the example:
class RouteGenerator {
static const main_home= "/main";
static Route<dynamic> generatorRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case main_home:
return MaterialPageRoute(builder: (_) => MainHome());
break;
}
}
}
This class must be add to the Main in:
MaterialApp( onGenerateRoute: ->RouteGenerator.generatorRoute)
Now to use this method, just write:
Navigator.of(context).pushNamedAndRemoveUntil(
RouteGenerator.main_home,
(Route<dynamic> route) => false
);