Dispose is not being called - flutter

I've got a stateful Widget that's on the navigation stack; HomeScreen()
For some reason, when it should be removed from the stack, it doesn't appear to be calling dispose().
I remove HomeScreen from the navigation stack in two ways:
1) Using Navigator.pushReplacement()
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (context, a1, a2) => GroupScreen()));
2) Using Navigator.of(context).pushAndRemoveUntil()
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LandingScreen()),
(Route<dynamic> route) => false);
The dispose method in HomeScreen() is overridden with the following:
#override
void dispose() {
print("dispose called");
_timer.cancel();
print('Timer has been disposed');
super.dispose();
}
and it's apparent that it's not being called as the print statements are not output to the console.
It may be worth mentioning that the HomeScreen has a StreamBuilder, but I'm not too sure if that would impact things.
Any help would be much appreciated, thank you.

Nevermind, it turns out that I was using "MaterialApp" for one of the widgets in the build method rather than "Material", which was causing the issue.

Related

Flutter: How to perform an actions after Navigator.pushNamedAndRemoveUntil() has completed

In my Flutter app, I need to clear the Navigator stack and get back to the Home page when a certain button is pressed. To achieve that I've used Navigator.pushNamedAndRemoveUntil(context, "/", (r) => false);. I also need to call a function after the navigation has been completed, meaning that I'm now on the Home page.
I've tried calling the .whenComplete() method on Navigator.pushNamedAndRemoveUntil(), but it doesn't seem to work.
Thanks in advance.
I'd say use your function inside dipose(), so it's called when the widget is removed from the tree as you navigate to another screen.
class _MyPageState extends State<MyPage> {
// ...
// Some code
#override
void dispose() {
// Insert your function here
super.dispose();
}
// ...
}
Using didPush() method
This can be used to call your function after navigating to another screen because it returns when the push transition is complete. However, you have to do it with pushAndRemoveUntil() instead of pushNamedAndRemoveUntil(). So, you can create a PageRoute which provides didPush() method.
// Create your route
MaterialPageRoute route = MaterialPageRoute(
builder: (context) => HomePage(),
);
// Push the route onto the navigator, and then remove all the previous routes
Navigator.pushAndRemoveUntil(context, route, (r) => false);
// This returns when the push transition is complete.
route.didPush().whenComplete(() {
// Insert your function here
});
You can try as well as this code And please let me know.
gotoNewPage(context) async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
NewPage()));
/// Your logic is here.
}

Persistent BottomNavigationBar with Routing in Flutter

I have a hard time implementing a persistent BottomNavigationBar in Flutter. My goal is to create a app with several screens and therefore several routes (minimal example):
I found this medium article and after struggling a bit with the implementation, I thought that I found the perfect solution. BUT as I wanted to implement a logout function that sends the user back to the LoginScreen the routing doesn't work as expected...
As you see in the gif, the problem occours after clicking on the logout button. Instead of navigating back to the LoginScreen, the LoginScreen get's embedded into the MainScreen with the BottomNavigationBar.
How can I change this behaviour? I thought I would remove all routes with pushAndRemoveUntil...
// Navigate back to the LoginScreen (this doesn't work as expected...)
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
(Route<dynamic> route) => false);
Here is a minimal reproducable example: https://github.com/klasenma/persistent_bottomnavigationbar
After several attempts, I managed to solve the problem. I needed to save the context of the MainScreen (index.dart -> holds the BottomNavigationBar).
class ContextKeeper {
static BuildContext buildContext;
void init(BuildContext context) {
buildContext = context;
}
}
lib/screens/main/index.dart:
#override
void initState() {
super.initState();
ContextKeeper().init(context); // Save the context
}
Then change
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => LoginScreen(),),(Route<dynamic> route) => false);
to
Navigator.of(ContextKeeper.buildContext).pushNamedAndRemoveUntil(LoginScreen.id, (route) => false);
and it work's.

Flutter: UI reactions with Provider

On some event, I want to navigate to another screen with Navigator.
I could easily achieve it with BlocListener:
BlocListener<BlocA, BlocAState>(
bloc: blocA,
listener: (context, state) {
if (state is Success) {
Navigator.of(context).pushNamed('/details');
}
},
child: Container(),
)
But I can't find the direct equivalent for it in a pure Provider.
The only way I see is to swap screens:
home: Consumer<Auth>(
builder: (_, auth, __) => auth.user == null ? LoginPage() : MainPage()
)
It's a common way. But it will not use Navigator, hence it will just 'pop' MainPage without screen transition.
On some event, I want to play some animation in UI.
I found in the documentation that Listenable class is intended for dealing with Animations, but it's not explained in details.
On some event, I want to clear a TextEditingController.
On some event, I want to show a dialog.
And more similar tasks...
How to solve it? Thanks in advance!
After some research I found a way. I'm not sure if it's the only or the best way, or the way foreseen by Provider's creator, however it works.
The idea is to keep a helper Stream inside of my Store class (I mean business-logic class provided with Provider), and to subscribe to its changes in my widget.
So in my Store class I have:
final _eventStream = StreamController.broadcast();
Stream get eventStream => _eventStream.stream;
void dispose() {
_eventStream.close();
super.dispose();
}
I add events to this stream inside of actions:
void navigateToNextScreen() {
_eventStream.sink.add('nav');
}
void openDialog() {
_eventStream.sink.add('dialog');
}
In my UI widget I have:
#override
void afterFirstLayout(BuildContext context) {
context.read<Transactions>().eventStream.listen((event) {
if (event == 'nav') {
Navigator.push(
context,
MaterialPageRoute(
builder: (ctx) => SecondScreen(),
),
);
} else if (event == 'dialog') {
showDialog(
context: context,
builder: (context) => AlertDialog(content: Text("Meow")));
}
});
}
I used here afterFirstLayout lifecycle method from the after_layout package, which is just a wrapper for WidgetsBinding.instance.addPostFrameCallback
07.07.20 UPD.: Just found a package that can be used for event reactions:
https://pub.dev/packages/event_bus
It basically uses the same approach with StreamController under the hood.

flutter call a function after moving back to screen from another screen

How to call a function in flutter ,after moving back to a screen from another screen?
For Example:
Screen 1
function1(){
}
Screen2
function2(){
//Go back to screen 1 and then call function1()
}
It's simple.
Navigator.push(context, MaterialPageRoute(builder: (context)=> SecondScreen())).then((_){
// This method gets callback after your SecondScreen is popped from the stack or finished.
function1();
});
You should also refer the Flutter Navigation & Routing.
Here is the solution!
Second Screen
Navigator.pop(context, [1]);
or, if you don't want to send back any data, you can only call
Navigator.pop(context);
First Screen
Navigator.push( context, MaterialPageRoute( builder: (context) => SecondScreen(), ), ).then((value) { //do something after resuming screen
});
Imho the solutions provided here aren't valid solutions.
If you use a routes Future it may be called multiple times and will even be called in case of a forward navigation.
Instead use a NavigatorObserver:
class AppNavigationObserver extends NavigatorObserver {
#override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
print("AppNavigationObserver: ${route.settings.name}");
print("AppNavigationObserver: ${previousRoute?.settings.name}");
}
}
You can then use it for example like this:
MaterialApp(
navigatorObservers: [
AppNavigationObserver()
],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder(
maintainState: true,
settings: settings,
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) {
// Your route builder according to settings
},
);
},
)
The important part is passing onGenerateRoute's settings paramter to the PageRouteBuilder settings. Otherwise settings.arguments and settings.name will be null in the didPop handler.

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