Navigator error in flutter (Drawer button logout) - dart

I have a Drawer that i share in all my StatefulWidget like this
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
drawer: SharedDrawer()
... //More code
In the Drawer i put a LogOut button that redirect the user to the login page cleaning all the router stack like this.
Navigator.of(context).pop();
Navigator.of(context).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
but when i try to log in again to the app this error appears.
flutter: Looking up a deactivated widget's ancestor is unsafe. At this
point the state of the widget's element tree is no longer stable. To
safely refer to a widget's ancestor in its dispose() method, save a
reference to the ancestor by calling inheritFromWidgetOfExactType() in
the widget's didChangeDependencies() method.
what i'm doing wrong ?
how i can manage the login/out in the app or is something with the navigator stack ?
Regards!

The error "Looking up a deactivated widget's ancestor is unsafe" is usually caused by reusing reference of Widgets that has been disposed previously i.e. during Navigator.of(context).pop();
One way to solve this issue is to keep track the context that you're using on your widgets so it won't reuse previoulsy disposed contexts.

Related

Flutter Navigator.of(context).pop vs Navigator.pop(context) difference

What's the difference between Navigator.of(context).pop and Navigator.pop(context)?
To me both seems to do the same work, what is the actual difference. Is one deprecated?
Navigator.push(context, route) vs Navigator.of(context).push(route)
Navigator is used to manage the app's stack of pages(routes). When push the given route onto the screen(Navigator), We need to get the right Navigator and then push.
Navigator.of(context).push(route) splits .of(context) to get the right Navigator and .push(route). Navigator.of(context) has optional parameters, if rootNavigator is set to true, the NavigatorState from the furthest is given instead.
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
})
Navigator.push(context, route) is a static method and do both at the same time. It internally calls Navigator.of(context).push(route). The navigator is most tightly encloses the given context.
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
return Navigator.of(context).push(route);
}
pop() is similar to push().
When multiple Navigators are nested in App. The dialog route created by showDialog(...) method is pushed to the root navigator. If the application has multiple Navigator objects, it may be necessary to call Navigator.of(context, rootNavigator: true).pop(result) to close the dialog rather than just Navigator.pop(context, result).
There is no difference between the two, source code confirms this. Calling
Navigator.pop(context)
actually calls
Navigator.of(context).pop()
Source code:
static bool pop(BuildContext context, [ dynamic result ]) {
return Navigator.of(context).pop(result);
}
A little bit (not really a little) late to this but the main difference I notice between these two is that Navigator.pop(context) calls Navigator.of(context).pop() with the current widget's BuildContext.
Basically Navigator.of(context).pop() gets a NavigatorState from the passed context and pops the top-most route off the navigator. So when you use it directly, it pops the top-most route off the parent's Navigator (which is the route you are currently on). When you do Navigator.pop(context), you are doing Navigator.of(<current widget's context>).pop() which usually does the same thing since the current widget is usually on the top-most route.
To see differences between objects, you can try to check their hash code. For example with the code below, you can see if the function is being called with the same instance of BuildContext.
final navigatorState = Navigator.of(context);
print(navigatorState.context.hashCode); // Prints the parent's context's hash code.
print(context.hashCode); // Prints the current widget's context's hash code.
BUT this can become tricky when for example you call showDialog on the current context and your widget rebuilds, while the Dialog is still showing. In this case, If you try to pop the dialog with a Navigator.pop(context) you may encounter an Exception such as:
The following assertion was thrown while handling a gesture:
Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by
calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
In this use case, using Navigator.of(context).pop() would be better.

Widget re-render when I focus on a TextField - Bloc Pattern

I'm using a BLoC to keep state between two nested FullScreenDialogs.
I'm initializing the bloc when I push the first screen, like so
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => ProductBlocProvider(child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
ProductEntryScreen has a bunch of TextFields and a button than opens a new FullScreenDialog. This new Screen also has TextFields.
The problem I'm having is that every time I write on a TextField on the second FullScreenDialog, the onPressed function where I start the ProductBlocProvider runs again.
And that re-run is causing the Bloc to create a new instance, so I end up loosing the state.
What I want to do?
Maybe I'm doing it wrong so I'll explain what I'm trying to achieve.
I want to keep state between the two FullScreenDialogs while I fill all the fields, and when I'm done I want to press a button that send all of the data (both screens) to a database.
The problem is that I was creating the instance of the bloc inside the provider in the builder function of the MaterialPageRoute.
That builder function was being called repeatedly, and creating a new instance of the bloc every time. The solution was to take out from the builde function the creation of the bloc instance, like this:
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//Here I create the instance
var _bloc = ProductBloc();
Navigator.of(context).push(MaterialPageRoute(
//And I pass the bloc instance to the provider
builder: (BuildContext context) => ProductBlocProvider(bloc: _bloc, child: ProductEntryScreen()),
fullscreenDialog: true
));
},
);
The package get_it may be of help to you. get_it is a service locator library, and uses a Map to store the registered objects; therefore, it provides access at a complexity of O(1), which means it's incredibly fast. The package comes with a singleton GetIt which you can use like so,
// Create a global variable (traditionally called sl or locator)
final sl = GetIt.instance; // There is also a shorthand GetIt.i
// ...
// Then, maybe in a global function called initDi(),
// you could register your dependencies.
sl.registerLazySingleton(() => ProductBloc());
registerLazySingleton() or registerSingleton() will always
return the same instance; lazily (i.e., when first called)
or at app start-up respectively.
If you want to create a new instance every time, use registerFactory() instead (I put this here even though it's not exactly what you want).
For example,
sl.registerFactory(() => ValidatorCubit());
And it could be accessed like this,
MultiBlocProvider(
providers: [
// The type is inferred here
BlocProvider<AuthenticationBloc>(create: (_) => sl()),
// The type is explicitly given here
BlocProvider(create: (_) => sl<ProductsBloc>()),
],
child: ProductsScreen(),
),
This example primarily shows you how it can be done with the flutter_bloc library, but get_it works anywhere, even in non-flutter dart projects.
If you need more functionality, do make sure to read the docs for this package. It is well documented, and contains (almost) every feature you might need, including scoping.
Also, this approach allows you to use the interface pattern, making the code much more maintainable and testable, as you will have to change just one place to use a different implementation.

What are Keys in the Stateless widgets class?

In the flutter docs there's sample code for a stateless widget subclass as shown:
class GreenFrog extends StatelessWidget {
const GreenFrog({ Key key }) : super(key: key);
#override
Widget build(BuildContext context) {
return new Container(color: const Color(0xFF2DBD3A));
}
}
and this
class Frog extends StatelessWidget {
const Frog({
Key key,
this.color: const Color(0xFF2DBD3A),
this.child,
}) : super(key: key);
final Color color;
final Widget child;
#override
Widget build(BuildContext context) {
return new Container(color: color, child: child);
}
}
What is a key and when should this super constructor be used? It seems like if you have your own constructor you must have {Key key} why? I've seen other examples where the super keyword is not used so this is where my confusion is.
TLDR: All widgets should have a Key key as optional parameter or their constructor.
Key is something used by flutter engine at the step of recognizing which widget in a list as changed.
It is useful when you have a list (Column, Row, whatever) of widgets of the same type that can potentially get removed/inserted.
Let's say you have this (code not working, but you get the idea) :
AnimatedList(
children: [
Card(child: Text("foo")),
Card(child: Text("bar")),
Card(child: Text("42")),
]
)
Potentially, you can remove any of these widgets individually with a swipe.
The thing is, our list has an animation when a child is removed. So let's remove "bar".
AnimatedList(
children: [
Card(child: Text("foo")),
Card(child: Text("42")),
]
)
The problem: Without Key, flutter won't be able to know if the second element of your Row disappeared. Or if it's the last one that disappeared and the second has its child change.
So without Key, you could potentially have a bug where your leave animation will be played on the last element instead!
This is where Key takes place.
If we start our example again, using key we'd have this :
AnimatedList(
children: [
Card(key: ObjectKey("foo"), child: Text("foo")),
Card(key: ObjectKey("bar"), child: Text("bar")),
Card(key: ObjectKey("42"), child: Text("42")),
]
)
notice how the key is not the child index but something unique to the element.
From this point, if we remove "bar" again, we'll have
AnimatedList(
children: [
Card(key: ObjectKey("foo"), child: Text("foo")),
Card(key: ObjectKey("42"), child: Text("42")),
]
)
Thanks to key being present, flutter engine now knows for sure which widget got removed. And now our leave animation will correctly play on "bar" instead of "42".
What are Keys?
Keys are IDs for widgets. All widgets have them, not just StatelessWidgets. They are used by the Element tree to determine if a widget can be reused or if it needs to be rebuilt. When no key is specified (the usual case), then the widget type is used to determine this.
Why use Keys?
Keys are useful for maintaining state when the number or position of widgets changes. If there is no key then the Flutter framework can get confused about which widget changed.
When to use Keys?
Only use them when the framework needs your help to know which widget to update.
Most of the time you don't need to use keys. Since keys are mostly only useful for maintaining state, if you have a stateless widget whose children are all stateless, then there is no need to use a key on it. It won't hurt to use a key in this case, but it also won't help.
There are some micro-optimizations you can make using keys. See this article.
Where to use Keys?
Put the key at the part of the widget tree where the reordering or addition/deletion is taking place. For example, if you are reordering the items of a ListView whose children are ListTile widgets, then add the keys to the ListTile widgets.
What kind of Keys to use?
A key is just an id, but the kind of ID you use can vary.
ValueKey
A ValueKey is a local key that takes a simple value like a string or integer.
ObjectKey
If you widget is displaying more complex data than a single value, then you can use an ObjectKey for that widget.
UniqueKey
This type of key is guaranteed to give you a unique ID every time. If you use it, though, do NOT put it in the build method. Otherwise your widget will never have the same ID and so the Element tree will never find a match to reuse.
GlobalKey
GlobalKeys can be used to maintain state across your app, but use them sparingly because they are similar to global variables. It is often preferable to use a state management solution instead.
Examples of using Keys
AnimatedLists
Changing the position in a column/row
TextFormField
References
Keys! What are they good for?
Using Keys in Flutter
Key is object that is used to identify a widget uniquely.
They are used to access or restore state In a StatefulWidget (Mostly we don't need them at all if our widget tree is all Stateless Widgets).
There are various types of key that I will try to explain on the basis of usage.
Purpose (key types)
1. Mutate the collection i.e. remove / add / reorder item to list in stateful widget like draggable todo list where checked items get removed
➡️ ObjectKey, ValueKey & UniqueKey
2. Move widget from one Parent to another preserving it's state.
➡️ GlobalKey
3. Display same Widget in multiple screens and holding its state.
➡️ GlobalKey
4. Validate Form.
➡️ GlobalKey
5. You want to give a key without using any data.
➡️ UniqueKey
6. If you can use a certain fields of data like UUID of users as unique Key.
➡️ ValueKey
7. If you do not have any unique field to use as key but object itself is unique.
➡️ ObjectKey
8. If you have multiple Forms or Multiple Widgets of the same type that need GlobalKey.
➡️ GlobalObjectKey, LabeledGlobalKey whichever is appropriate, similar logic to ValueKey and ObjectKey
❌ Do not use random string/number as key, it defeats the purpose of keys ❌
The Key is an optional parameter needed to preserve state in your widget tree, you have to use them if you want to move a collection of elements in your tree and preserve the state of them.
The best explanation can be found in this video by Google When to Use Keys - Flutter Widgets 101 Ep. 4
With Dart 2.12 or later, add ? after Key to make it optional if you want.
class Frog extends StatelessWidget {
const Frog({
Key? key,
this.color: const Color(0xFF2DBD3A),
this.child,
}) : super(key: key);
final Color color;
final Widget child;
#override
Widget build(BuildContext context) {
return new Container(color: color, child: child);
}
}

Type of Navigator.push() can't be signed to onPressed

Im looking through flutter docs and examples and im stuck in the routing.
Here is documentation https://docs.flutter.io/flutter/widgets/Navigator-class.html
It tells I can do a button with function to navigate with Navigator
onPressed: (context) {
Navigator.pop(context);
}
but my ide (vs code) and debugger doesn't think it is possible. It shows me error when im trying to do it.
[dart] The argument type '(dynamic) → Null' can't be assigned to the parameter type '() → void'.
Build also fails with exceptions
setState() or markNeedsBuild() called during build.
This Overlay widget cannot be marked as needing to build because the framewo…
not quite sure what it means...
Any suggestions?
You don't need to pass context to the onPressed callback, context is available inside the callback anyway if the callback is created in a scope where context is available:
build(BuildContext context) {
...
onPressed: () {
Navigator.pop(context);
}
}
Do not pass the context to onPressed as it is a void callback, so it does not return anything. Use it only to execute logic.

Observer for Navigator route changes in Flutter

Is there a way to listen to route changes of the Navigator in Flutter? Basically, I'd like to be notified when a route is pushed or popped, and have the current route on display on screen be returned from the notification
Building on navigator observers, you can also use RouteObserver and RouteAware.
Navigator has observers. You can implement NavigatorObserver and receive notifications with details.
I was also struggling with that and for my purpose the RouteObservers felt overkill and where too much for my needs. The way I handled it lately was to use the onGenerateRoute property inside my MaterialApp. So this solution is more applicable if you are using onGenerateRoute with your app (might especially be useful if you are navigating with arguments). You can read more about it here.
My MaterialApp looks like the following:
runApp(MaterialApp(
title: 'bla',
home: BuilderPage(LoginArguments(false)),
onGenerateRoute: generateRoute
));
The generateRoute method looks like the following:
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'main':
print('Navigated to main')
return MaterialPageRoute(builder: (_) => MainScreen());
case 'register':
print('Navigated to register')
return MaterialPageRoute(builder: (_) => RegisterScreen());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}')),
));
}
}
So everytime I now do for example:
Navigator.pushNamed(
context,
'register',
);
It will print Navigated to register. I think this way is especially helpful if you don't necessarily need to know whether you pushed or popped. With some additional implementation it would also be possible to observe that via injected arguments.

Resources