Flutter Navigator.pushReplacementNamed show back button at the page - flutter

I'm using Navigator.pushReplacementNamed in flutter app to navigate from loginpage to homepage working well, but in my homepage show back arrow button at the appBar and it returns to loginpage when pressing it. What should I do? I tried put leading: Text(''), but when pressing physical back button it still goes back to loginpage.
I have a logout button and I want user to logout only by this button, not from back button
*this is my first question here, sorry for my poor English or any mistakes

You should use this instead:
.pushNamedAndRemoveUntil(/* Your Route */, (Route<dynamic> route) => false)

As I understand your UX flow, the user will always be directed to the login page on the app start. If the user is already logged in, you should avoid navigating to this (at that moment useless) route. Instead try to distinguish in the build method if the user is logged in or not. If the user is already logged in, build the homepage. If the user is not logged in, build the login page. As soon as the user logs in, the state changes and the homepage will be build.
// your stateful component
bool isLoggedIn;
#override
Widget build(BuildContext context) {
if(isLoggedIn) {
return _buildHomepage();
} else {
return _buildLoginPage();
}
}
Widget _buildHomepage() {
// build your homepage
}
Widget _buildLoginPage() {
// build your login page
}

I had this problem today and came across your Post.
While PushNamedAndRemoveUntil works, the solution in my case was even more simple:
Make sure you're not Naming a route, which is not your Homescreen '/'.
Something like:
MaterialApp(
title: 'Flutter Demo',
initialRoute: Screen1.routeName, // routeName = "/route1"
routes: {
Screen1.routeName: (ctx) => Screen1(), // routeName = "/route1"
Screen2.routeName: (ctx) => Screen2(), // routeName = "/"
Screen3.routeName: (ctx) => Screen3(), // routeName = "/screen2"
},
);
})
Will start your App with Screen1, but place Screen2 at the top of the Navigator's Stack.

Related

How can I implement navigation routes in flutter

I have design my scenario to understanding about the flow.
Dashboard Screen=> open side Drawer =>Select any menu=> First screen=> second screen=>open dialog(second screen)=> First screen=> Side drawer.
I want to use above like this navigation flow, when I click inside the dialog button I want to go First screen than first screen to click back button so go to side drawer.
I'm using the below code for navigate screen.
Navigator.of(context).pushNamed(screenName);
Please suggest me to how I can implement my above mention flow.
you can give each screen a route name like this :
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
"/home_page" : (context) => MyHomePage(),
"/keep_alive": (context) => const KeepAliveExample()
},
initialRoute: "/home_page",
);
}
}
like this you will set all of your routes , and set an initial route that the application will run at the start.
when you want to navigate to another screen you will do this :
Navigator.of(context).pushNamed(screenName);
and the screen name will be the route that you give it in the material app.
you can change the pushNamed to others styles navigations like :
Navigator.of(context).pushNamedAndRemoveUntil(newRouteName, (route) => false);
Navigator.of(context).pushReplacementNamed(routeName);
Navigator.of(context).popAndPushNamed(routeName);

Flutter web URL navigation like http://localhost:60117/ItemDetails?pId=1 not working

In my flutter project, i have to share a URL which is directly redirect to a particular product details page through social media . so i have added routing-information using velocity_x, my main.dart page is as follows
#override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primaryColor: MyAppTheme.primaryColor,
accentColor:MyAppTheme.accentColor
),
routeInformationParser: VxInformationParser(),
routerDelegate: VxNavigator(routes: {
"/": (_,__)=> MaterialPage(child:SplashScreen()),
"/login": (_,__)=> MaterialPage(child:SignUp()),
"/home": (_,__)=> MaterialPage(child:Home(selectedIndex: 0)),
"/ItemDetails": (uri, _){
final pId=int.parse(uri.queryParameters["pId"]);
return MaterialPage(
child:ItemDetailsFromAdd(
pId:pId.toString() ,
));
},
}),
)
My Initial route is splash screen and after checking authentication it redirects to login page or home page. when i click on a particular item, creates a url "http://localhost:60117/ItemDetails?pId=1" this. But when i try to launch this url from another tab, first it load product detail page and suddenly redirects to my initial page splashscreen.
I tried to change "/" initial route from "/" to "/splash" and changed my "<base href="/splash/">" but it gives a page not found error initially.
How can i access my product detail page directly using this "http://localhost:60117/ItemDetails?pId=1" URL in correct way.
onGenerateRoute: (settings) {
List<String> pathComponents = settings.name.split('/');
if (pathComponents[1] == 'invoice') {
return MaterialPageRoute(
builder: (context) {
return Invoice(arguments: pathComponents.last);
},
);
} else
return MaterialPageRoute(
builder: (context) {
return LandingPage();
},
);
;
},
Try this way
Try adding a hashtag # to the URL:
http://localhost:60117/#/ItemDetails?pId=1
It's the only way to share it if you are running a progressive web app. What's the left of the /#/ tells the browser from where to get the app, and what's after it is the router logic. If you try to remove the /#/, the app doesn't behave normally and reverts back to the default route.
After long testing i found the answer, here my initial route ('/') redirect to the'SplashScreen'. so when i try to access particular item using URL first it comes to SplashScreen and there i coded to redirect to home or Login based on the user authentication. so when i access to a particular item using url it redirect to my login/home. the solution is i changed my initial route to Home page.

Navigating away when a Flutter FutureProvider resolves it's future

I'm basically looking for a way to either start loading data, or navigate to the Login Screen.
The FutureProvider gets it's value from SharedPreferences. The default homescreen is just a logo with a spinner.
If the userID resolves to null, the app should Navigate to the Login Screen, otherwise it should call a method that will start loading data and then on completion navigate to the main page.
Can this be achieved with FutureProvider?
I add it to the page build to ensure the page widget will subscribe to the Provider:
Widget build(BuildContext context) {
userInfo = Provider.of<UserInfo>(context);
print('Building with $userInfo');
return PageWithLoadingIndicator();
....
I added it to didChangeDependencies to react to the change:
#override
void didChangeDependencies() {
print('Deps changed: $userInfo');
super.didChangeDependencies();
// userInfo = Provider.of<UserInfo>(context); // Already building, can't do this.
// print('And now: $userInfo');
if (userInfo == null) return;
if (userInfo.userId != null) {
startUp(); // Run when user is logged in
} else {
tryLogin(); // Navigate to Login
}
}
And since I can't use Provider.of in initState I added a PostFrameCallback
void postFrame(BuildContext context) {
print('PostFrame...');
userInfo = Provider.of<UserInfo>(context);
}
Main is very simple - it just sets up the MultiProvider with a single FutureProvider for now.
class MyApp extends StatelessWidget {
Future<UserInfo> getUserInfo() async {
String token = await UserPrefs.token;
return UserInfo.fromToken(
token,
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App One',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home: MultiProvider(
providers: [FutureProvider<UserInfo>(builder: (_) => getUserInfo())],
child: LoadingScreen(),
),
);
}
}
The problem is that as it is now I can see from the print statements that "didChangeDependencies" gets called twice, but the value of userInfo is always null, even though build() eventually gets an instance of UserInfo as evident by the print statement in the build() method.
I'm guessing I can add some logic into the build method but that screams against my sensibilities as the wrong place to do it... perhaps it isn't as bad as I think?
I've decided that this is conceptually the wrong approach.
In my case I wanted to use a FutureProvider to take the results from an Async function which create a "Config" object using SharedPreferences. The FutureProvider would then allow the rest of the app to access the user's config settings obtained from sharepreferences.
This still feels to me like a valid approach. But there are problems with this from an app flow perspective.
Mainly that the values from the shared preferences includes the logged in user session token and username.
The app starts by showing a Loading screen with a Circular Progress bar. The app then reads the shared preferences and connects online to check that the session is valid. If there is no session, or if it is not valid, the app navigates to the Login "wizzard" which asks username, then on the next page for the password and then on the next page for 2-factor login. After that it navigates to the landing page. If the loading page found a valid session, the login wizzard is skipped.
The thing is that the two things - app state and app flow are tangenially different. The app flow can result in changes being store in the app state, but the app state should not affect the app flow, at least not in this way, conceptually.
In practical terms I don't think calling Navigator.push() from a FutureProvider's build function is valid, even if context is available. I could be wrong about this, but I felt the flowing approach is more Flutteronic.
#override
void initState() {
super.initState();
_loadSharedPrefs().then((_) {
if(this.session.isValid()) _navToLandingPage();
else _navToLoginStepOne();
}
}
I'm open to better suggestions / guidance

How to restore last route in Flutter

I'm developing a Flutter app and I have a problem.
In my main.dart I set a home route that is "FirstScreen", then the user can go to the LoginScreen to sign into the account. So if I press the home button and then I try to re-open the app the screen that appear is FirstScreen, so how can I show the last route the user have seen?
I've searched on Flutter docs and on other question in StackOverflow for some solutions but I found nothing that work for me.
This is my main.dart build.
Widget build(BuildContext context) {
return MaterialApp(
home: FirstScreen(),
routes: {
'/screen1' : (context) => Screen1(),
'/homeScreen' : (context) => HomeScreen(navigatorKey: navigatorKey,),
'/registerScreen' : (context) => RegisterScreen(),
'/screen2' : (context) => Screen2(),
'/firstScreen' : (context) => FirstScreen(),
'/userProfileScreen' : (context) => UserProfileScreen(),
},
navigatorKey: navigatorKey,
);
You'll need to keep track of the latest route the user has visited and store that somewhere persistent so that it survives app restarts. Perhaps look at the shared preferences package as a simple approach:
https://pub.dev/packages/shared_preferences
When the app starts, look in shared preferences to see if you have the previous route stored, and navigate to it (or set it as the home route). If not, fall back to the FirstScreen as a default.
I've find a solution, using WidgetBindingObserver, and put this line : WidgetsBinding.instance.addObserver(this); inside every StatefulWidget initState.

Something like conditional routing in Flutter?

Consider I have 3 screens namely screen 1, screen 2, screen 3 and screen 4.
I want to achieve the following.
Screen 3 is opened by Screen 1 -> BackButton -> Screen 2
Screen 3 is opened by Screen 2 -> BackButton -> Screen 3
Screen 3 is opened by Screen 4 -> BackButton -> Screen 1
Moreover, iOS automatically sets a swipe back option. I want to overwrite it that a swipe back in iOS does the same as described above.
Is there something like conditional routing in Flutter which helps me to adjust the BackButton-behaviour in accordance to 'from which Screen was my current Screen opened (navigator.push)'?
Wrap your widget tree in a WillPopScope() widget. This widget has an onWillPop property that you can override to whatever you want - in this case, depending on the screen you're on you'll probably want to override it to
onWillPop: () => Navigator.pushReplacement(<correctScreenWidget>)
This should catch any attempts to go back and instead do whatever you override it to. Be sparing with it, overriding default back button behaviour can make for a weird user experience if done poorly.
As for the conditional part of it, unfortunately it's a bit tricky as Navigator._history is private, so we can't just check the previous route that way. Best bet is to set up a NavigatorObserver to keep track of previous routes, and set the name in the RouteSettings of each of your routes to keep track.
Step one is to create an observer and provide it to your Navigator, something like this:
class PreviousRouteObserver extends NavigatorObserver {
Route _previousRoute;
Route get previousRoute => _previousRoute;
String get previousRouteName => _previousRoute.settings.name;
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
_previousRoute = previousRoute;
}
#override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
_previousRoute = oldRoute;
}
}
class MyApp extends StatelessWidget {
final PreviousRouteObserver observer = PreviousRouteObserver();
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Flutter Demo Home Page', observer: observer),
navigatorObservers: [
observer,
],
);
}
}
Note that MyHomePage above needs to accept the observer as an argument so you can access it. Alternatively you could set up an InheritedWidget or something to maintain access to it, but this answer is getting a little long already so I'll leave that for a later question.
Then, when providing Routes to your Navigator, ensure you've got a name in the RouteSettings:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => NextScreen(observer: widget.observer),
settings: RouteSettings(name: "nextScreen"),
),
);
Finally, do conditional routing based on the current value of widget.observer.previousRouteName in any widget that has access to it. This is just a simple matter of a switch or something in your onWillPop, so I'll leave that to you.
Kind of unfortunate that it's so roundabout, but it looks like this might be your best option at the moment. Hope it helps!