flutter Navigator in initState, not working - flutter

i have code like this from the internet, and my is not working:
class _StartingAnimationState extends State<StartingAnimation>{
#override
void initState() {
Future.delayed(Duration(seconds: 4),(){
Navigator.push(context, MaterialPageRoute(builder: (context) {
return mainWindow();
}));
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SizedBox(height: 300,width: 300, child: Lottie.asset("assets/lottie/login.json")),
);
}
}
I got error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
and
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.
I want my program to show animation for some time, and then navigate to other screen

You can't push a new route in initState. How ever you can register a post frame callback that can run a function.
In you build method, before the return statement:
WidgetsBinding.instance.addPostFrameCallback((_){
Future.delayed(Duration(seconds: 4),(){
Navigator.push(context, MaterialPageRoute(builder: (context) {
return mainWindow();
}));
});
});
return MaterialApp(
home: SizedBox(height: 300,width: 300, child: Lottie.asset("assets/lottie/login.json")),
//home: SizedBox(height: 300,width: 300, child: Lottie.asset("assets/lottie/login.json")),
);

i find answer for eanyone who will find this question
answer is:
i didnt put MaterialApp in my main:
runApp(MaterialApp(home: StartingAnimation()));
and also i saw mistake in my initState:
#override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 4),(){
Navigator.push(context, MaterialPageRoute(builder: (context) {
return mainWindow();
}));
});
}

Why it's not working?
The MaterialApp configures the top-level Navigator to search for navigation routes in order:
For the / route, the home property, if non-null, is used.
Otherwise, the routes table is used, if it has an entry for the route.
Otherwise, onGenerateRoute is called, if provided. It should return a
non-null value for any valid route not handled by home and routes.
Finally if all else fails onUnknownRoute is called.
That's why error comes when the above approach did not follow Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
Solution!!!
Wrap StartingAnimation with MaterialApp
runApp(MaterialApp(home: StartingAnimation()));
Note: This only happens when navigation is done on the route widget, if done on other widgets/screens, it will not occur as MaterialApp already initialized as everyone added at the top.

Related

Flutter : How to Pop with two nested Navigators?

I have a MaterialApp(navigatorKey: rootNavigatorKey ) , which has two routes
'/datoPhotoViewer' without a drawer
'/' which has a Drawer, this drawer has listItems that pushes the widgets on his child : Navigator(navigatorKey: navigatorKey ),
The problem is when i press the back button, whole app closes, My idea was then, catch Back-Button-pressed , and calling the navigatorKey.currentState.pop() from the adequate navigatorKey
So to do it, i need WillPopScope .. when I set a WillPopScope on each route, i find that when i press Back button, the app was still closing
So i've decided to delete all the WillPopScope i had, and put only one WillPopScope(onWillPop: onPop) on main.dart where by trying, i find that it catches every 'back button pressed', and changing that static function onPop when i open a route with :
setOnPopFunction(Function func){
onPop = func;
}
But when i call this function, onPop function isn't changed ,
Another thing i saw happening : I'm getting logged those weird results when accessing the navigatorKeys
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
Function onPop ;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
onPop = (){
log("___Navigator1 context "+ ModalRoute.of(rootNavigatorKey.currentContext).toString());
//this prints '___Navigator1 context null' , even though i can push /datoPhotoViewer to Navigator 1
log("___Navigator2 routeName "+ ModalRoute.of(navigatorKey.currentContext).settings.name.toString());
//always printing '___Navigator2 routeName /' ,even when this navigatorKey has been pushed to another route
return Future.value(false);
};
return MaterialApp(
navigatorKey: rootNavigatorKey, //Navigator 1 key
routes: {
'/': (context) { //Navigator 1 route
return WillPopScope(
onWillPop: onPop //this is the only onWillPop function i can get called when pressing back button , Which i'll change with setOnPopFunction()
child: MyMainStructure( //MyMainStructure is a Scaffold with the drawer
child: Navigator(
key: navigatorKey, //Navigator 2 key
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': //Navigator 2 route
return MaterialPageRoute(
builder: (_) => TodosLosItemsPage());
case '/categoria': //Navigator 2 route
return MaterialPageRoute(
builder: (_) =>
CategoriaConceptosPage(settings.arguments),
);
}
},
initialRoute: '/', //Navigator 2 initial
)),
);
},
'/datoPhotoViewer': (context) => //Navigator 1 route
datoPhotoViewer(ModalRoute.of(context).settings.arguments),
},
initialRoute: '/', //Navigator 1 initial
),
);
}
}
setOnPopFunction(Function func){
onPop = func;
}
thanks in advance
To solve this i ended up using an external package
https://pub.dev/packages/back_button_interceptor
I've disabled the back button for all the app with a
WillPopScope(
onWillPop: () async => false,
child: ....
And making a BackButtonManager class to override the Back Button
List<Function> _listeners = List<Function>();
class BackButtonManager {
static void addCustomFunction(Function func){
_listeners.add(func);
BackButtonInterceptor.add(func);
}
static void resetListeners(){
_listeners.forEach((element) {
BackButtonInterceptor.remove(element);
});
_listeners.clear();
}
This is the structure of what i do when a new route is opened, on initState i call:
BackButtonManager.resetListeners();
BackButtonManager.addCustomFunction ((bool asd){
//handle it
//for example: navigatorKey.CurrentState.pop();
// then check which widget is going to be onscreen now ,
// and when this BackButton is pressed , add to BackButtonManager it's
// BackButtonFunction
BackButtonManager.resetListeners();
BackButtonManager.addCustomFunction ((bool asd){
//handle the incoming OnScreenWidget
return false;
});
return false;
});
I'd recommend to make a list that represents your stacks of routes, there might be another way , but this works

Provider ValueNotifier listen not working on some Pages on Flutter

listen is not working. when hot reload value is updated.
Page A
#override
Widget build(BuildContext context) {
ValueNotifier<List<ProductModel>> selectNotifier = Provider.of<ValueNotifier<List<ProductModel>>>(context, listen: true);
Widget
Text('${selectNotifier.value.length}'),
Page B
InkWell(
onTap: () {
selectNotifier.value.add(selectProduct);
},
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ValueNotifier<List<ProductModel>>>(
create: (_) => ValueNotifier<List<ProductModel>>([]),
),
],
child: MaterialApp(
theme: CustomTheme.themeData,
onGenerateRoute: Router.generateRoute,
debugShowCheckedModeBanner: false,
),
);
}
Version
provider: ^4.1.2
Flutter Version
1.17.2
I tried below ways to fix this issue. But I don't know what is right way(best way).
1st way
After downgrade Flutter and Provider, now is working. why is that?
provider: 3.2.0
git checkout v1.12.13-hotfixes
2nd way
Or it is working this way too.//but warning on the IDE
onTap: () {
selectNotifier.value.add(selectProduct);
selectNotifier.notifyListeners(); //info: The member 'notifyListeners' can only be used within instance members of subclasses of 'package:flutter/src/foundation/change_notifier.dart'.
},
But warning disappear, after adding this ChangeNotifier,
class _viewState extends State<View> with ChangeNotifier{
and also getting error after adding ChangeNotifier
The following assertion was thrown while finalizing the widget tree:
_CartItemViewState.dispose failed to call super.dispose.
dispose() implementations must always call their superclass dispose()
method, to ensure that all the resources used by the widget are fully
released.
3rd way
I don't get any issue on this way, but I used so many ValueNotifier in my production app, so, others are not a List. I don't know how to change other types.
onTap: () {
selectNotifier.value = List.from(selectNotifier.value)..add(widget.productModel);
}
In this question, 3rd way is the correct way.
onTap: () {
selectNotifier.value = List.from(selectNotifier.value)..add(widget.productModel);
}

Navigate to Additional Page Seconds after First Loads

Say my app starts on PageA, I'm trying to navigate to PageB 2 seconds after it loads.
Here is what I've tried...
In PageA:
Future _sleepTwo() {
return new Future.delayed(const Duration(seconds: 2), () => "sleeping...");
}
#override
void initState() {
this._sleepTwo().then((res) {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => PageB(),
));
});
}
But this gives me the following error:
E/flutter (22949): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
E/flutter (22949): 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.
I've looked everywhere but got nowhere.
I even tried wrapping everything in initState() with a Future(() => {code here});. But it didn't work.
You won't have a context if the page hasn't initialized. Insert the following code into your Widget build(BuildContext context) method.
_sleepTwo().then((res) {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => PageB(),
));
});
Don't use the await keyword if you want the PageA to still draw itself, and then after two seconds switch to PageB

How to get a context for Navigator in Widget initState()?

I want my app to work offline without a user set, and asking for a login when connectivity is back
en excerpt of the code I'm trying:
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) =>
checkConnectivity().then((isOnline) {
if (isOnline && MyApp.store.state.user == null)
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()),
);
}));
}
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
...
but all I can get is this error:
Unhandled Exception: 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.
I tried to wrap my Navigator call inside a Future.delayed as described here but I got the same error

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