Logout from non-Widget class - flutter

My app makes multiple requests to the server. Sometimes the server may ask the user to re-login, similar to this question:
Flutter: how to force an application restart (in production mode)?
I could do something like this,
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false);
but I need to have a BuildContext for this.
Is there a way to get a current (most recently used) context while in a non-Widget class? I know I can pass the context as an argument every time I'm making a server call, but I hope to find a less intrusive way to do this.

The reason you are looking for context is because you'd like to get hold of the Navigator. (All the Navigator.pushXXX calls do a Navigator.of(context) under the hood to find the (typically) one and only navigator up near the top of the widget tree.)
If what you really want is just access to the navigator, there's another way to do this. Make your app stateful and put a global key in its state.
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
In the app's build (or top inherited widget's build), when you create the material app, pass it the key.
#override
Widget build(BuildContext context) {
return _SomeInherited(
data: this,
child: MaterialApp(
navigatorKey: navigatorKey,
title: 'Some Title',
theme: someTheme,
home: FrontPage(),
),
);
}
Now, from the level of the app, you can use navigatorKey.currentState.pushXXX

Related

Keep Navigator state data (provider/InheritedWidget) when pushing routes

In my flutter app I have a list of instances, when user clicks one I open the instance page and provide the InstanceData model to it using
Navigator.push(context, MaterialPageRoute(builder: (context) =>
ChangeNotifierProvider(
create: (_) => InstanceData(...),
child: Instance()
)
));
This does work, but when the Instance widget does Navigator.push, the provider data is lost because my provider is below the app's Navigator.
I googled for a solution and found that I can do:
Navigator.push(context, MaterialPageRoute(builder: (context) =>
ChangeNotifierProvider(
create: (_) => InstanceData(),
child: Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => Instance()
);
},
)
)
));
Here's the dartpad to show it: https://dartpad.dev/?id=51fcbabb1ad401afb193558ccde3b5e1
Now, all (even deeply nested) widgets that are opened from Instance() will have access to the InstanceData. Also, I actually need multiple nesting. E.g. we click Instance and create Provider for it, then inside Instance we click Area and I want all widgets from Area() and below to have access to both InstanceData and AreaData, so I will need to create another Navigator to hold AreaData. Think of it as in this example:
https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf
which shows that we keep separate history inside each Tab. Now, if each my Instance is in its own Tab, and Instance also has tabs panel with each tab being Area that also has to keep its own history - that's what I need.
However I am new to flutter and I am not sure if that's the correct approach. Creating own Navigator on each route push just so that my Inherited/Provider data is accessible? Looks like a hack to me.
E.g. what if I want to show two Instance() widgets side by side - I would wrap them with their own Navigator just to pass the data but isn't navigator supposed to track pages/screens instead? How will it behave with the Back button?
So can somehow confirm the solution and if not, them suggest a state management for flutter that will allow to create do something like
Navigator.push(context, MaterialPageRoute(builder: (context) =>
CoolProvider(
create: (_) => InstanceData(...),
child: Instance()
)
));
where CoolProvider does work for any Navigator.push() inside any child widget of the Instance(), even if that's some "CoolNavigator.push" instead. Maybe it's some kind of sub-routes? E.g. Navigator.pushSubRoute() so that the main route is still there?
Maybe it's not even about Navigator? E.g. in the side-by-side Instances having Navigator is almost meaningless - user can't navigate to both instances at once, but I do need each Instance() to be clickable and to go to nested Areas with the InstanceData() available to those widgets.
Update: In fact I tried it side by side here:
https://dartpad.dev/?id=3c299f3972ac18a1b70b841380eaffef
and it seems to be working fine, because without the Navigator the push just replaces the whole page, while with navigator it correctly replaces just the half-side widget.
So is it the way I suppose to do? Any drawbacks - need to use navigatorKey, etc?
P.S. There are of course alternatives like passing InstanceData to each child ctor but that's a lot of manual properties passing around... having the Provider is surely a better way.

How to scope providers on several widgets without putting the provider on a root widget in flutter

I'm working on a simple demo where the user logs in, sees a list of items, clicks on an item, and then sees its details. These are 3 screens but let's say two flows where one deals with login and the other with items.
I'm managing my state using provider package. I placed a LoginRepository provider on my root widget because that state is needed everywhere. This works great.
Now I need an ItemRepository which should be available on the ItemListScreen and the ItemDetailScreen. To implement that scoping, I decided to create a common ancestor widget called ItemScreens. This is where I placed my provider and my nested Navigator.
I navigate my screens using Navigator.of(context) to make sure the closes ancestor navigator is used. I fetch my provider using Provider.of<ItemRepository>(context) to make sure the closes ancestor state is used.
This apparently does not work because I get a Could not find the correct provider. I understand that the provider from the sibling route cannot be accessed by another sibling but here I'm nesting navigators I would expect the ItemRepository to be placed at ItemScreens which then handles subrouting so that these two routes can fetch providers from the parent route?
Sidenote:
Flutter documentation clearly states that providers should be lifted to achieve proper scoping. But I've never seen an example where the provider was lifted to a non-root widget. If all we can do is place providers into the root widget, that should be clearly stated.
EDIT: Re-creating providers on several widgets is kind of an alternative but that's not really scoping it's just pseudo passing by value.
EDIT: Putting all providers on the root widget with MultiProvider isn't a good idea because it creates a global state anti-pattern. Some states should also simply not be created unless needed i.e. the user is visiting that flow/screen.
You can wrap your MaterialApp Widget in main.dart with Multiprovider and give the list of providers you are using, That way App knows which class to treat as providers.
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => FirstProvider(),
),
ChangeNotifierProvider(
create: (_) => SecondProvider(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Auro',
home: _decideMainPage(context),
// routes: routes,
theme: AllCoustomTheme.getThemeData(),
),
)

Navigate to inner page when on another page

How can I navigate to an inner page of a PageView when I'm on another page. For example: say I have the following pages:
Home
All Messages > Single Message Page
Contact
When I'm on the Contact page, how can I go straight to the Single Message Page? I feel it's easiest to use named routing, so would love to have sucha sollution....
As you mentioned in your own question, named routing would be the right way to do so.
With named routing, the route tree is much easier to understand and move around.
For Example, I defined in my main app widget, the exact routes in my app.
As you can see from the Flutter documentation:
MaterialApp(
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/',
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => FirstScreen(),
// When navigating to the "/second" route, build the SecondScreen widget.
'/second': (context) => SecondScreen(),
},
);
After you have defined the routes, you can move to this route by pushing a named route to the Navigator of your app.
// Within the `FirstScreen` widget
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/second');
}
Read more at https://flutter.dev/docs/cookbook/navigation/named-routes
Wish you luck!

Calling Navigator.of anywhere without context?

I've been setting up a tabbed navigation app based on this wonderful tutorial (https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf).
Now I would like to display a modal overlay login route that covers the whole screen. My login controller checks if the user is logged in and I would like to fire an event on which the modal login route appears. The problem I have is now, that I don't have a context object where I receive the signal to display the login route:
Navigator.of(context).pushReplacementNamed('/');
How can I solve this or is this the wrong approach?
My User controller is a singleton object that gets initiated at app start. It checks then the user data model and if that is not set, it wants to invoke the login screen / route.
Thanks for any pointer in the right direction.
Martin
Define a navigator key that accesses from everywhere in-app (e.g in main class global space ) and pass it to root MaterialApp navigator key property in the build method
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
then :
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
//...
);
}
then you can access context everywhere with doing like this:
navigatorKey.currentContext
example of navigating:
Navigator.of(navigatorKey.currentContext).pushReplacement(
MaterialPageRoute(builder: (_) => SecondScreen()));
You could use a globalKey to access the context of the widget u like (you have to pass it a key in the constructor) , and leave it in the global space, or static in any class....
although not the most elegant approach, should work
you can use this package to skip the required context.
https://pub.dev/packages/one_context
// go to second page using named route
OneContext().pushNamed('/second');
// go to second page using MaterialPageRoute
OneContext().push(MaterialPageRoute(builder: (_) => SecondPage()));

Flutter - How to handle Navigation without using MaterialApp?

In flutter creating named routes is easy and logical, but only when returning a MaterialApp.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
initialRoute: "/";
return MaterialApp( //gives errors when I return a Container
routes: {
"/" : (context) => FirstRoute(),
"/second route" : (context) => SecondRoute()
}
);
}
}
I am not that big a fan of Material Design and want to create a UI from my own design.
But when I apply the same pattern when returning a container I get an error.
So my qustion is how do I have named route setup with a vanilla Flutter App or am I being forced to use MaterialApp for my project?
Thanks in advance
MaterialApp is just a collection of commonly used components such as a navigator. You could also use a CupertinoApp. Material uses iOS navigation animations on iOS and Android animations on android. Though you’re not stuck to the UI design because you’re using a MaterialApp as a foundation. You are able to build whatever UI you want with a material app or even use Cupertino widgets. It’s all up to you.