Flutter navState context - flutter

I searched for a way to navigate through notification and i had to find solution for using context out of widget scoped so i created Global variable and Navigator state and used it, it worked fine.
Now i'm thinking why i have to pass every route push the context of the widget, why not use the navigator state on every route push.
import 'package:flutter/material.dart';
/// Global variables
/// * [GlobalKey<NavigatorState>]
class GlobalVariable {
/// This global key is used in material app for navigation through firebase notifications.
/// [navState] usage can be found in [notification_notifier.dart] file.
static final GlobalKey<NavigatorState> navState = GlobalKey<NavigatorState>();
}
adding to the MaterialApp
MaterialApp(
title: 'myapp',
navigatorKey: GlobalVariable.navState,
Navigator.push(GlobalVariable.navState.currentContext, MaterialPageRoute(builder: (BuildContext context) => RoutePage(child: ContactForm())));
instead of
(context) => Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => RoutePage(child: ContactForm())));

Related

how do i use provider in this situation

I want to create a change app theme mode and I saw a way of creating it with Provider but I'm new to Provider. For Example, I want to add some codes like this
(the highlighted code)
in my main which consists of many routes
You want to change the theme of the app, then you need to move provider up so it can cover the widget (App in this case) state,
You could do something like this in your main method :
runApp(ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child:MyApp()
);
now in the case of children you could simply call provider in the build method like this
Widget build(){
var themeProvider = Provider.of<ThemeProvider>(context);
}
or you could use the consumer widget
Consumer<ThemeProvider>(
builder: (context, provider, child) {
//return something
}
)
I suggest you to move your ChangeNotifierProvider to your runApp() method
runApp(
ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider(),
child: MyApp(),
),
),
Where your MyApp() is just all of your app extracted to its own widget.
Then you can actually easily access it as you wish with a Consumer widget on your build method.
return Consumer<ThemeProvider>(
builder: (BuildContext context, ThemeProvider provider, _) {
return MaterialApp(
theme: provider.myTheme,
...
);
}
)

how to make a validation before navigating to a route?

I have 2 pages, page1 andpage2. I want to validate that when the app is opened and there is no token or it is false, it redirects to page1 otherwise it redirects to page2, and when I have more pages I want that if there is a valid token, continues the normal flow of the navigation, I was trying this and I have this problem:
in the gif the token is not defined, the validation apparently does well, but the problem is that it continues to reload the current view, I am looking for something more optimal that avoids loading a route if some condition is not met
how can I solve that?
Map<String, WidgetBuilder> getRoutes() {
return <String, WidgetBuilder>{
'/': (BuildContext context) =>
checkNavigation("/", pag1(), context),
'page1': (BuildContext context) =>
checkNavigation("page1", page1(), context),
'page2': (BuildContext context) =>
checkNavigation("/page2", page2(), context)
};
}
dynamic checkNavigation(
String page, dynamic pageContext, BuildContext context) {
if (storage.token && page == "/") {
//Navigator.pushNamedAndRemoveUntil(context, 'page2', (_) => false);
return page2();
} else if (storage.token == false) {
//Navigator.pushNamedAndRemoveUntil(context, 'page1', (_) => false);
return page1();
} else {
return pageContext;
}
}
in my main:
.
.
.
MaterialApp(
title: 'route validation',
initialRoute: '/',
routes: getRoutes(),
It's better to control this behavior in your own abstractions and change routes only if necessary.
I would recommend to add some splash screen at root route and navigate to appropriate route, once token is initialized.
Future<void> asyncInit() {/*...*/}
void initState() {
/* ... */
asyncInit().then((_) => /* push appropriate first route */);
}
Map<String, WidgetBuilder> getRoutes() {
return <String, WidgetBuilder>{
'/': (BuildContext context) => SplashScreen(),
/* other routes */
};
}
If you need intercept other navigation events you can add your own proxy class, this may be easily implemented using Provider package
class MyNavigator {
final GlobalKey<NavigatorState> navigatorKey;
MyNavigator(this.navigatorKey);
static MyNavigator of(BuildContext context) => context.read<MyNavigator>();
Future<T> pushNamed<T extends Object>(
BuildContext context,
String routeName, {
Object arguments,
}) {
// add any additional logic and conditions here
return navigatorKey.currentState.pushNamed<T>(routeName, arguments: arguments);
}
// add any other methods you need
}
// somewhere at the top of widget tree above Widgets/Material/CupertinoApp widget.
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// add provider with navigator key above navigator
Provider(
create: (_) => MyNavigator(navigatorKey),
child: MaterialApp(navigatorKey: navigatorKey, /*...*/)
)
// use it
MyNavigator.of(context).pushNamed(...)
There is much work going right now to implement Navigator 2.0 with Router and Pages API, which gives you more control and flexibility on routing.
tracking issue:
https://github.com/flutter/flutter/issues/45938
design docs:
https://flutter.dev/go/navigator-with-router
https://flutter.dev/go/router-and-widgetsapp-integration
Pages API is already available in current stable release, but there is not enough documentation and examples at the moment.

access bloc from Navigator.pushNamed in flutter

I have a routing like this:
routes: {
'/': (context) => MainPage(),
'/othe_page': (context) => OthePage(),
},
Navigator.pushNamed(context, '/othe_page');
How can I pass bloc to OthePage widget?
1) passing bloc as a argument
You have to pass your bloc as argument in navigator as below.
Navigator.pushNamed(context, '/deletewidget',
arguments: bloc);
Now you have to accept that bloc in new page as below.
class _DeleteWidgetState extends State<DeleteWidget> {
var bloc;
#override
Widget build(BuildContext context) {
bloc = ModalRoute.of(context).settings.arguments;
Note: you have to create bloc variable out of build method and assign bloc variable in build method.
-when you return to last screen then you will not able to access bloc anymore.
2) you can use bloc provider in parent widget so that you can access your bloc in new screen also.
for more detail check out Flutter_bloc package.

Flutter showDialog with navigator key rather than passing context

Currently its very hectic to show dialog from any layer of code in app just because one has to pass context in it. Hence i thought to pass navigatorKey.currentContext (Navigator key is a global key passed to Material app navigatorKey parameter) to show dialog. But i got the error
"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 issue is showDialog calls Navigator.of(context) internally and which looks for the navigator ancestor which ofcourse will return null as the navigator is itself the root. Hence it will not find the navigator as ancestor.
Is there a way we can directly pass the navigator state/context to showDialog function to show the dialog? Or is there a more easy way to show Dialog without passing context to it if we want to show it from bloc?
I found a simple solution:
navigatorKey.currentState.overlay.context
I use this in a redux middleware where I keep navigatorKey, and want to show a dialog globally anywhere in the app everytime I dispatch a specific action.
Since this one is merged:
https://github.com/flutter/flutter/pull/58259
You can use:
navigatorKey.currentContext;
You can make use of InheritedWidget here. Make a InheritedWidget the root for your application which holds a navigator key. Then you can pass any context of child widgets to get the current navigator state.
Example:
InheritedWidget:
// Your InheritedWidget
class NavigatorStateFromKeyOrContext extends InheritedWidget {
const NavigatorStateFromKeyOrContext({
Key key,
#required this.navigatorKey,
#required Widget child,
}) : super(key: key, child: child);
final GlobalKey<NavigatorState> navigatorKey;
static GlobalKey<NavigatorState> getKey(BuildContext context) {
final NavigatorStateFromKeyOrContext provider =
context.inheritFromWidgetOfExactType(NavigatorStateFromKeyOrContext);
return provider.navigatorKey;
}
static NavigatorState of(BuildContext context) {
NavigatorState state;
try {
state = Navigator.of(context);
} catch (e) {
// Assertion error thrown in debug mode, in release mode no errors are thrown
print(e);
}
if (state != null) {
// state can be null when context does not include a Navigator in release mode
return state;
}
final NavigatorStateFromKeyOrContext provider =
context.inheritFromWidgetOfExactType(NavigatorStateFromKeyOrContext);
return provider.navigatorKey?.currentState;
}
#override
bool updateShouldNotify(NavigatorStateFromKeyOrContext oldWidget) {
return navigatorKey != oldWidget.navigatorKey;
}
}
HomeScreen:
// Your home screen
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: NavigatorStateFromKeyOrContext.getKey(context),
home: InitPage(),
);
}
}
The root of the application will look like,
final GlobalKey navigator = GlobalKey<NavigatorState>(debugLabel: 'AppNavigator');
runApp(
NavigatorStateFromKeyOrContext(
navigatorKey: navigator,
child: HomePage(),
),
);
Now from anywhere in the app, pass any context to get the NavigatorState like
NavigatorStateFromKeyOrContext.of(context)
Note: This is one approach I came up with where I used InheritedWidget, there are many other ways to achieve the same, like using Singleton, having a global bloc to provide navigator key, storing the navigator key in a Redux store or any other global state management solutions, etc.
Hope this helps!
Currently, I am showing a dialog by creating a function in my util class which takes the context as a parameter.
static void showAlertDialog(String title, String message, BuildContext context) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text(title),
content: new Text(message),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Using the above function as:
UtilClass. showAlertDialog("Title", "Message", context);

How to navigate without context in flutter app?

I have an app that recieves push notification using OneSignal. I have made a notification opened handler that should open specific screen on click of the notification. How can i navigate to a screen without context. or how can I open specific screen on app startup. My code:
OneSignal.shared.setNotificationOpenedHandler((notification) {
var notify = notification.notification.payload.additionalData;
if (notify["type"] == "message") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DM(user: notify['id']),
),
);
}
if (notify["type"] == "user") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Profileo(notify["id"]),
),
);
}
if (notify["type"] == "post") {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ViewPost(notify["id"]),
),
);
}
});
I am able to achieve this when the app is opened for the first time but It only opens the homepage If i close the app and even if I re-open it. I guess that is because the context is changed.
Please Help!!
Look at this here:
https://github.com/brianegan/flutter_redux/issues/5#issuecomment-361215074
You can set a global key for your navigation:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Pass it to MaterialApp:
new MaterialApp(
title: 'MyApp',
onGenerateRoute: generateRoute,
navigatorKey: navigatorKey,
);
Push routes:
navigatorKey.currentState.pushNamed('/someRoute');
You can use this wonderful plugin:
https://pub.dev/packages/get
Description from the package: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars from anywhere in your code without context.
Get.to(NextScreen()); // look at this simplicity :)
Get.back(); // pop()
Get.off(NextScreen()); // clears the previous routes and opens a new screen.
This solution is general if you want to navigate or to show dialog without context using globalKey especially with Bloc or when your logic is separated from your UI part.
Firstly install this package:
Not: I'm using null safety version
get_it: ^7.2.0
Then create a separate file for your service locator:
service_location.dart
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!.pushNamed(routeName);
}
void setupLocator() {
locator.registerLazySingleton(() => NavigationService());
}
void showMyDialog() {
showDialog(
context: navigatorKey.currentContext!,
builder: (context) => Center(
child: Material(
color: Colors.transparent,
child: Text('Hello'),
),
));
}
}
on main.dart:
void main() {
WidgetsFlutterBinding.ensureInitialized();
NavigationService().setupLocator();
runApp(MyApp());
}
// add navigatorKey for MaterialApp
MaterialApp(
navigatorKey: locator<NavigationService>().navigatorKey,
),
at your business logic file bloc.dart
define this inside the bloc class or at whatever class you want to use navigation inside
Then start to navigate inside any function inside.
class Cubit extends Cubit<CubitState> {
final NavigationService _navigationService = locator<NavigationService>();
void sampleFunction(){
_navigationService.navigateTo('/home_screen'); // to navigate
_navigationService.showMyDialog(); // to show dialog
}
}
Not: I'm using generateRoute for routing.
Quickest fix is above using global navigatorKey (like #tsdevelopment answered).
To fix undefined navigatorKey, it must be imported from where it is instantiated (for this example in main.dart).
Your main.dart
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() {
runApp(CupertinoApp(
title: 'Navigate without context',
initialRoute: '/',
navigatorKey: navigatorKey, // important
onGenerateRoute: ...
));
}
For example you are in your lib/utils/api.dart
import 'package:your_package_name/main.dart'; // important
abstract class API {
static Future<dynamic> get() async {
// call some api
...
// then you want to navigate to specific screen like login
navigatorKey.currentState?.pushNamed('/login'); // navigate to login, with null-aware check
}
}
Also have a gist example if you prefer in a service approach.
Check this: https://gist.github.com/josephdicdican/81e59fad70530eac251ad6c28e2dcd4b
I know this is an old post, but there is a package that handles navigation without the build context (Using a navigator key) called flutter_navigator: https://pub.dev/packages/flutter_navigator
It allows you to navigate something like this:
_flutterNavigation.push(//YourRoute);
Everything seems to be mapped 1:1 with Flutter's Navigator API, so there is no worries there!
You can use this no_context_navigation package
as the name suggests, we can navigate without context
navService.pushNamed('/detail_screen', args: 'From Home Screen');