Open HQiang opened 5 years ago
It would be nice if you attach some problem code.
Because there is a lot of code, I can't paste it. I probably tested it myself. This happens when the current page is the last page of the application. Clicking Back will exit the application. At this time, the Widget and the Widget are included. The dispose() of all custom widgets is not executed. I wrote a demo that indicates what I want to express.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppHomeWidget(),
);
}
}
class AppHomeWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return AppHomeState();
}
}
class AppHomeState extends State<AppHomeWidget>{
@override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
body: Column(
children: <Widget>[
CustomWidget(),
Text("text")
],
),
);
}
@override
void dispose() {
super.dispose();
print("AppHomeWidget dispose");
}
}
class CustomWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return SecondScreenState();
}
}
class SecondScreenState extends State<StatefulWidget> {
@override
Widget build(BuildContext context) {
return Center(
child: new Text('CustomWidget')
);
}
@override
void dispose() {
super.dispose();
print("CustomWidget dispose");
}
}
This demo has only one page. This page is also the last page of the application. In my actual project, it is the project's home page. Before it has a guide page, the login page is just that they have been closed. The home page is the end of the application. A page, similar to my demo, I don't know why, AppHomeWidget dispose and CustomWidget dispose are not printed.
In short, when the last page of the application is closed, all widgets' dispose() on this page will not be executed.
@dblokhin Why the thumbs down if this code reproduces the problem?
@tvolkert is this by design?
@escamoteur the code was not fenced and highlighted. Currently it's nice. :)
I think this is a big problem. When I want to quit the application, I want to release some resources. Dispose() does not execute. I really don't know how to implement the resource shutdown. I think this problem should be faced by everyone. How to solve it? I want the answer, I am going crazy.
@HQiang What you can do use https://api.flutter.dev/flutter/widgets/WillPopScope-class.html for this.
@escamoteur Ok, thank you, rest on the weekend, I will try the company next Monday, thank you again, thank you
A question is why the state of CustomWidget
extends State<StatefulWidget>
. I wasn't able to check it but my guess is its the problem. I don't think it is a proper way to make a stateful widget.
Hey you have a sharp eye. It has to be State
CustomWidget extends State<StatefulWidget>
No problem, in actual projects, you might write extends States<CustomWidget>
, but this has no effect. In fact, you can test your own Flutter project to see when the application exits. All StatefulWidget State dispose() on the last page will not be executed. I just came into contact with Flutter and I encountered this problem. As escamoteur said, is this designed? If this is the case, then the last page Widgets will never be removed from the tree? Is it because I didn't write the code according to the rules, but it is really an entry-level code structure, I don't understand.
Yeap, I can reproduce the problem. dispose()
is never called also on hot restart.
Still reproducible in 1.21
. I updated the code to make the state classes to not expect a StatefulWidget but its own widget type.
I was facing a similar issue but I think the problem stemmed from calling super.dispose();
before anything else ... I've moved it to the end of the dispose function and seems ok now .. Not sure if this is why others are facing issues though, and I don't really understand what the super method is for 😄
even examples from official documentation trigger this bug: https://flutter.dev/docs/cookbook/networking/web-sockets#complete-example
why is no one working on it since 2019? was State.dispose()
deprecated? if so, what should be used instead to reliably release resources on app exit?
you have to register a handler for app live cycle events.
@escamoteur do you suggest it as a temporary workaround or are you implying that State.dispose()
should not be relied on? the latter is contrary to many of the current docs, but this may be a bug in docs. Could you please provide a link to an authoritative source if you think this State.dispose()
should not be used/relied on?
Thanks!
Where is stated that it will get called in case the App gets killed?
@escamoteur now you are just trolling: from the source docs:
/// Subclasses should override this method to release any resources retained /// by this object (e.g., stop any active animations).
moreover, there's a lot of examples in the official docs that use dispose()
for this purpose, you can find one instance in the link I provided before.
However, as I wrote before, these may be bugs in the docs. That's why I asked if you happen to have any link to an authoritative docs that say that dispose()
should not be used/relied on for this purpose?
it is always called when the widget is removed from the widget tree. I don't think it is guaranteed when the app is killed. What resources do you have to dispose in such a case?
@Hixie could you confirm, that this behavior is by design?
@escamoteur this bug is exactly about the fact that it should be called when the app quits in a normal way (such as pressing back on the last mobile screen, closing browser tab, closing desktop app window). This is the behavior that everyone expects, including core flutter libraries authors, as you can see from the websocket example. This is also the behavior that source documentation describes.
Unless there's a really strong reason not to call it when a view is getting destroyed because of app's exit, it should be fixed. The main reason is that otherwise you will have to expose the resources that are local to a particular screen, so that the app life cycle handlers can clean them up. This breaks abstraction and encapsulation rules.
Finally, if a strong reason not to fix it indeed exists (which I personally doubt), the current documentation should be fixed.
I'm not sure if the framework has the time to do that in case of an app being closed by back presses as Android rather drastically closes Apps. the other question is which resources do you have to release in such a case that aren't released anyway by the OS if the App is killed? BTW, I would be a bit careful who you call trolling
@escamoteur since it is possible to do it in the app life cycle handlers as you wrote, it strongly suggests that there generally is the time for it. Moreover, pressing 'back', can be handled programmatically and does not need to remove the current view at all (for example in android chrome browser, it loads the previous page without removing fragment/activity), so a simple solution is for the flutter framework to do it this way on mobile (by overriding default 'back' button behavior, which I believe it already does in general, as separate flutter views are not mapped 1-1 with android activities/fragments AFAIK)
network connections using any protocol that requires some kind of a terminal message before closing is a good example of a resource that is not cleaned properly by the OS. Failure to send a terminating frame may result for example in a transaction being undesirably rolled back by a program on the other side of the connection. (to some extent a websocket is such a protocol: it requires an explicit notification before closing. if you run the above linked example on a browser, it will automatically send a terminating frame if you close the tab, as browsers are aware of websocket protocol. However if you run it on mobile or desktop, the final frame will no be sent and you will receive error on the server side).
Do a bit of research on App live cycling on Android, it's a very unpleasant topic :-) From what I read so far you have to use a plugin to securely handle all live cycle events on Android. Flutter uses several threads so I'm not sure if the UI thread that is responsible of the Widget tree would get information fast enough to react.
As you say a Webbrowser is aware of the Websockets which make all the difference.
Also I would never ever handle a network protocol in the UI layer. So you have to hook into the live cycle events anyway if you really need to. On the other side a backend should be totally able to cope with any interruption because mobile devices can always loose connections. I'm not even sure if there is a way to react on the termination of an application on Windows because there is no concept of live cycle events there.
Easiest way is to use WillPopScope
if you want to react on the backbutton and manage this intentionally instead of relying on the live cycle event.
IMHO the reason this hasn't gotten more attention is that in most cases it isn't any real problem.
And it's also not an error in the documentation of Stateful Widget as ending an app is outside the normal UI events. However it is probably a good idea to add a comment that this case isn't covered at the moment.
@escamoteur if a network connection is a bound and local to a particular UI screen (ie, should be closed once a user willfully navigates away from this screen), it makes perfect sense to close it in dispose
. For example, if a user willingly navigates away from a chat screen, it makes perfect sense to notify other members that he has left.
The framework should make it easy for developers to do it: they should not be forced to complicate their code by disposing resources both in dispose
(in case this view was not at the bottom of the stack) and then again check if they were disposed properly in life-cycle handlers (in case it was at the bottom). As i wrote before, this also forces a bad design on devs as they need to expose resources that should remain private.
If a programmer can use life-cycle handlers to manually check if all resources were released, then the framework can use exactly the same mechanism to make sure dispose
was called on all widgets. The fact how complicated android app life-cycle is does not have anything to do here: if it works for devs, it works for framework also. it's just a matter if the framework forces devs to do this work manually, or if it does the expected tasks for them.
Disagree. Dispose is for disposing UI element subscriptions or animation controllers and the like and not to model app logic. If you willfully navigate away from a screen you can call a disposal function at that point.
And if an OS doesn't offer a live cycle event or gives the App only a limited amount of time to react before a process is just killed it's not like you can do everything. If possible you shouldn't rely any app logic on that events.
a more 'complete' example: if a user willingly leaves an online game by navigating away from a game screen (for example in a 'safe location' where games allows to safely stop playing and finally let the player go to sleep ;-) ), his character should be removed from the game instead of letting other users take advantage of stale player who can be easily looted.
Again, that would be core business logic that should not depend of the state of a Widget. It would be something I always call explicitely
Disagree. Dispose is for disposing UI element subscriptions or animation controllers and the like and not to model app logic.
well, you have a right to your opinion of course, but that's not what majority of devs expects, including core libs devs. Finally, as I explained, the current situation forces more complicated code and bad design.
If you willfully navigate away from a screen you can call a disposal function at that point.
dispose
should exactly be telling devs: "hey, the user just decided to navigate away". This is exactly what this bug is about.
And if an OS doesn't offer a live cycle event or gives the App only a limited amount of time to react before a process is just killed it's not like you can do everything. If possible you shouldn't rely any app logic on that events.
as I already wrote before this is incorrect: if a dev can use a life-cycle handler, the framework can use it also.
well, you have a right to your opinion of course, but that's not what majority of devs expects, including core libs devs. Finally, as I explained, the current situation forces more complicated code and bad design.
I have no idea who core lib devs are (I'm publishing packages since almost 3 years now). It's exactly what is stated in the docs. And IMHO you get worse design if you connect you app logic to a Widgets disposal function that is clearly intended for a Widget to clean up itself.
If you willfully navigate away from a screen you can call a disposal function at that point.
dispose
should exactly be telling devs: "hey, the user just decided to navigate away". This is exactly what this bug is about. That is you interpretation of you wantdispose
to be.And if an OS doesn't offer a live cycle event or gives the App only a limited amount of time to react before a process is just killed it's not like you can do everything. If possible you shouldn't rely any app logic on that events.
as I already wrote before this is incorrect: if a dev can use a life-cycle handler, the framework can use it also. Please study a bit more the problems people all around have with life-cycle events. I don't say it might not be possible the question is how much complexity it adds for something that can be solved easily differently. And for a cross platform framework it has to work everywhere. Win32 e.g. doesn't have a concept for reacting when an app closes.
This issue has 5! likes and the Flutter team has limited resources. if it were such a pressing issue it had way more likes.
I have no idea who core lib devs are (I'm publishing packages since almost 3 years now).
I believe websocket support is a core library. you could probably find more examples.
And IMHO you get worse design if you connect you app logic to a Widgets disposal function that is clearly intended for a Widget to clean up itself.
neither UI widgets nor network connections are exempted from object-oriented design rules: if a parent object owns a child object (meaning that child object is created/initialized in parent's initialization code and is supposed to be destroyed/released when the parent is destroyed/released), then the child object should be released in parents final life-cycle method. The fact that parent is UI widget and child is either a connection, an animation or whatever else, does not affect object-oriented design rules. Stating that "this is widget's life-cycle method called at the end of life, but you are allowed to release only UI related child objects in it: for any other child objects find yourself another way" does not follow good practices of object-oriented design in my opinion.
dispose
should exactly be telling devs: "hey, the user just decided to navigate away". This is exactly what this bug is about.That is you interpretation of you want
dispose
to be.
I believe that this is what object-orient design rules suggest and what majority of flutter devs expect.
And if an OS doesn't offer a live cycle event or gives the App only a limited amount of time to react before a process is just killed it's not like you can do everything. If possible you shouldn't rely any app logic on that events.
as I already wrote before this is incorrect: if a dev can use a life-cycle handler, the framework can use it also.
Please study a bit more the problems people all around have with life-cycle events. I don't say it might not be possible the question is how much complexity it adds for something that can be solved easily differently.
in the same sentence you mention how many problems ppl have with life-cycle events and then suggest that the child resource releasing can be solved easily by using life-cycle events, I presume as this is the way you suggested in the first message. Or do you have something else in mind? apologies if I misunderstood.
And for a cross platform framework it has to work everywhere. Win32 e.g. doesn't have a concept for reacting when an app closes.
I haven't been using windows for many years now indeed, but I'm pretty sure that when I close a notepad (or another file editing app) with unsaved changes, instead of closing immediately, the app will display a popup asking if changes should be saved before quitting.
This issue has 5! likes and the Flutter team has limited resources. if it were such a pressing issue it had way more likes.
https://stackoverflow.com/questions/61611415/dispose-method-not-called-in-flutter https://stackoverflow.com/questions/52571976/dispose-doesnt-dispose-completely https://stackoverflow.com/questions/58893259/flutter-provider-dispose-method-not-called-when-exiting-the-app https://stackoverflow.com/questions/54599561/flutter-navigation-and-the-requirement-for-dispose-in-the-mean-time ... I could be copy-pasting links for another 20 minutes or so... People get constantly confused by this, ask in many places on the web, some of them finally arrive here, they see that the issue has been open for 2 years and no one is assigned and give-up. Dismissive answers suggesting "where did you come up with such a crazy idea that this method should get called" from someone with 'contributor' badge do not encourage people to participate in the discussion either. Neither do threatening people when they point it out.
I found this comment by @Hixie in a similar, closed issue:
The dispose method on State should not be called when the app quits.
The behaviour of the following three cases should all be the same:
- Hitting the home button
- Hitting the back button on the first page
- Switching applications in the app switcher
I'd like to point that the behavior in the first case will never be the same in general because of how android works:
in the 1st and 3rd case, the app will generally not be terminated immediately (unless the system is tight on resources). Therefore if a user navigates back to the app via 'recent apps' switcher, the old connection will still be open (unless server closes it due to application specific timeout of client inactivity) and the server will not even notice that the user navigated away and returned again.
However, in the 2nd case, the app is always terminated immediately and all its OS resources released. It may be removed from the app switcher or not (depending on android version/flavor, but not sure here). If it's not removed and the user navigates back to it, the old state will have been already discarded by this time, a new instance of state will be created and a new connection to the server will be established.
Therefore, since the state is always destroyed in the 2nd case (but not necessarily in 1st and 3rd case), I claim that there should be a reliable way to release state's child resources according to the general rule of resource ownership mentioned before. Since dispose
method already exists and many devs expect it to be exactly this mechanism, I claim that dispose
method should be guaranteed to be called on app exit.
If there is a desire to unify behavior in the above 3 cases, then the only way to achieve this is by reliably terminating app in all 3 cases and thus reliably calling dispose
also. (which personally I think is a terrible idea: I don't want to lose app's state whenever I switch to another for 10 seconds to copy some text).
Generally speaking you can't rely on anything happening when the OS terminates your app. Many platforms (including iOS, Android, Linux, and Web) give you little to no notice that you are being terminated at the best of times, and at the worst of times (power outage, sudden unexpected physical destruction of the hardware, kernel panic, application crash) not even the operating system gets any notice.
All of which means you cannot rely on dispose
being called during shutdown. For this reason, we prefer to model the framework as if shutdown was not something that happened.
You can listen to the lifecycle events though if you want to try to do some cleanup on application shutdown. It won't be reliable, but it's possible. This can either use the framework hooks or you can write plugin code to provide platform-specific behaviour.
@Hixie
Generally speaking you can't rely on anything happening when the OS terminates your app. Many platforms (including iOS, Android, Linux, and Web) give you little to no notice that you are being terminated at the best of times, and at the worst of times (power outage, sudden unexpected physical destruction of the hardware, kernel panic, application crash) not even the operating system gets any notice.
All of which means you cannot rely on
dispose
being called during shutdown. For this reason, we prefer to model the framework as if shutdown was not something that happened.
there has always been some totally unrecoverable situations, like fiber getting cut, data-center getting blown away etc, but so far they were never considered to be a good reason to give up on a graceful termination in situations when it is possible. On the contrary: given the growing popularity of protocols like gRPC and long-living stream connections in general, it gets more and more important to properly terminate connections, so that the recovery/resuming is easier or even at all possible.
I understand that reliability of such mechanisms will differ from platform to platform, but I claim that on each particular one an effort should be made to make it as reliable as possible:
onDestroy
may be usedAgain, I understand that these mechanisms have various degrees of reliability (on linux SIGSEGV cannot be handled, on android onDestroy
may not get called if the system kills the whole process of an inactive app, etc), but each of them is way better than just giving-up. Otherwise, Flutter as a platform will be less reliable in several situations comparing to each platform's natively developed apps, which in turn will force devs back to native development in such cases.
You can listen to the lifecycle events though if you want to try to do some cleanup on application shutdown. It won't be reliable, but it's possible. This can either use the framework hooks or you can write plugin code to provide platform-specific behaviour.
is there any strong reason why Flutter shouldn't itself automatically listen to these events (as reliably as feasible) and make sure that each state's dispose
gets called? As mentioned before, it takes away this burden from a developers, promotes simpler and cleaner code (many developers will basically expose parts of states that should remain private to the life-cycle even handlers), which are pretty good reasons, I think.
Please reconsider pros and cons of this decision.
is there any strong reason why Flutter shouldn't itself automatically listen to these events (as reliably as feasible) and make sure that each state's
dispose
gets called?
I mean, I described the reason in my last comment. You may not agree that it's strong, but it's the reason. We believe apps would be better if they were designed to assume they will never get shutdown notifications, and instead are written to assume they might disappear without notice at any time. Writing an app that way means it works great in the same situations that an app would work great if it did have shutdown notifications, and works great in all the other cases where an app that relies on shutdown notifications would have a terrible user experience.
Sometimes, if dispose
is not called, deactivate
is called.
I had another scenario. If we have any button as actions
of appBar and from that if we navigate to any page, previous page's dispose is not called but deactivate is called.
Here's another vote for a graceful shutdown. My application is registering as a service on the local network via DNS-SD. DNS-SD requires the application to unregister once the service is not available anymore. Without unregistering, DNS-SD will list the dead service as available to clients.
A graceful shutdown would help a lot in minimizing those situations. Of course, if the OS absolutely SIGKILL s the application, that would be pointless to implement.
Another example of the absurdity of this situation. I have implemented some push notifications and I need to save the state of the application so that later, in the background function of the push notifications, I can make some decisions or others. A determining state that I need to store in the sharedpreferences is when the user terminates the application, that is, when it goes to detached state, because I cannot store it in the shared preference because detached already happened before sending the state to the didchangeapplifecycle function. How am I supposed to know what state it is in when I open the application, if I can't save the state before
Reproducible on both IOS and Android. Dispose method isn't called on the app exit.
This is also the case on Linux Desktop, dispose is not called on application normal exit. The only way around I found was to use native code and MethodChannel to call dispose() before exit. For those interested, this article give some details about ways to implement native code on different platforms.
Same here with another use case. Im playing background FM Radio source. if the app is shutdown, need to stop he signal and dispose the resources, if not the Radio will keep playing until another audio source send the singal to use the device main audio channel.
There seem to be a gap between what reporters tries to express and how flutter devs understand it.
@Hixie @escamoteur I don't think reporters want to be able to handle termination cases. If application is terminated by the OS, it is normal to not being able to execute any code in this process. That's really not the point reported here, reporters are talking about proper exit of the last screen or even process normal exit in case of desktop platforms and resource management consistency. Please do not disregard it as a termination case, that's not.
Disagree. Dispose is for disposing UI element subscriptions or animation controllers and the like and not to model app logic.
How can this be true ? Animations and UI elements subscriptions are not part of app logic then ? Animations are not the only resources that is tied to UI lifecycle, we can have a socket which is only meaningful while a certain widget is visible, we can want to release the NFC reader when going out of the screen asking for NFC device, etc... there are plenty of other cases possible.
Every argument you can bring on that this could be handled by other mean in the app logic could be applied to animations as well. If you are true, well, just let agree to remove the useless dispose
entirely then.
Just to rephrase the questioning then: Why do not you consider a normal exit of the process as being a proper event to trigger UI element disposal ?
I cannot find any other language runtime where process exit does not produce at least stack unwinding and let clean up our resources properly when that is feasible.
The problem is that there is no such thing as a normal exit on some of the platforms we support. For example, on iOS you literally cannot quit an app. If you do, it is considered equivalent to a crash.
If you want to just dispose the entire widget tree (running all the dispose methods etc) before quitting an app on platforms that support it, you could run runApp(SizedBox.shrink())
or similar before you tell the OS to close the application.
Of course, there is a level of abstraction brought by Flutter and all platforms cannot strictly adhere to the same terms. As we can effectively accept that there is no such thing as a process exit strictly speaking for mobile platforms as the OS (at least Android in my knowledge) will try to keep this process alive for running other activities/services/..., I'd argue that there is still an analogue to a "clean exit" in Flutter abstraction which is called SystemNavigator.pop(). On Linux platform, this is very close to a normal exit (if we do not take into consideration it still crashes and aborts the process 3 times out of 4 in my case).
Why this is not same as poping all widget from the tree before "exiting" the application ?
It makes very hard to consistently manage resources properly across all platforms. We have to hack around using either willPopScope
, didChangeAppLifecycleState
or either directly manipulating the code inside android/
, ios/
or linux/
and having to cautiously examine the changes brought by new flutter version in order to keep our custom changes.
Having a matching call between initState
and dispose
would help tremendously in these situations.
Let consider the following example of an application that binds to a TCP port when opening a Widget and closes it when widget is popped out of the tree. The TCP port is just for having a simple example to show and play with, but it can be any kind of resources, really.
It is the main.dart of the project targeting Android and Linux platforms, other files are identical to a bare flutter create --platforms android,linux
. (Except the new dependency ffi: ^2.0.2
in pubspec.yaml):
import 'dart:ffi' as ffi;
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/***************************************************************
FFI: Export symbols from libc to open and bind TCP socket
+ define struct definition of sockaddr_in
(it may feel overly complicated for an example, but I want to show an actual example for a resource
that effectively pose a problem if not released because it cannot be reacquired if kept too long.
exporting syscalls is for playing with TCP socket without bringing async/await which
would be way more complex for an example, ironically)
****************************************************************/
final class SockAddrIn extends ffi.Struct {
@ffi.Int16()
external int family;
@ffi.Int16()
external int port;
@ffi.Int32()
external int address;
@ffi.Int32()
external int zero1;
@ffi.Int32()
external int zero2;
}
class libc {
static final ffi.DynamicLibrary _lib = ffi.DynamicLibrary.process();
static final int Function(int, int, int) socket = _lib.lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int, ffi.Int)>>("socket").asFunction();
static final int Function(int, ffi.Pointer<SockAddrIn>, int) bind = _lib.lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Pointer<SockAddrIn>, ffi.Int)>>("bind").asFunction();
static final int Function(int, int) listen = _lib.lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>>("listen").asFunction();
static final int Function(int) htons = _lib.lookup<ffi.NativeFunction<ffi.Int16 Function(ffi.Int16)>>("htons").asFunction();
static final int Function(int) close = _lib.lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int)>>("close").asFunction();
}
/***************************************************************
END OF FFI:
****************************************************************/
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final int sockfd;
@override
void initState() {
// Bind a TCP socket to port 8080
sockfd = libc.socket(2, 1, 0);
ffi.Pointer<SockAddrIn> sockaddr = calloc<SockAddrIn>();
sockaddr.ref.family = 2;
sockaddr.ref.port = libc.htons(8080);
sockaddr.ref.address = 0;
var r = libc.bind(sockfd, sockaddr, 16);
if( r == 0 ) {
r = libc.listen(sockfd, 1);
}
if( r != 0 ) {
throw 'Failed to open TCP port 8080';
}
}
@override
void dispose() {
// release resource
libc.close(sockfd);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Push button to exit app',
),
ElevatedButton(
child: Text("Exit"),
onPressed: (){
SystemNavigator.pop();
},
),
],
),
),
);
}
}
We have now the problem that was reported here: dispose
is never called when exiting the application, even using SystemNavigator.pop
, and the resource management become complicated to handle portably across platforms.
Following the platforms, we would have to either:
didChangeAppLifecycleState
but we does still need to implement dispose
in case the widget is removed from the tree but we stay in our application.linux/main.cc
to intercept the fact that g_application_run
returned and be able to rerun a base application in order to clean the previous tree and trigger all dispose
as you mentioned in your last comment. Each flutter upgrade which refactors this file may break our code. (Handling this case in our app logic which executes an "exit" - like the onPressed
handler here - is not a solution because users may exit the application using the close button of the window).These two solutions are either adding complexity or completely not maintainable in the longterm. And this add inconsistency in the way we manage resources.
The inconsistency can be expressed by doing an analogy to how Android let manage lifecycle of Activities. If an activity is popped out of the stack, onDestroy
will be called. Wouldn't you find it weird that for some reason, the last activity for the backstack will just be destroyed without onDestroy
being called ? The State's dispose
has this kind of inconsistency and I'd guess a very high number of developers does not expect this.
If you run the given example project in Android, you'll experience the fun fact that crashes end up being a safer exit condition than proper "app exit", since during crash, the OS will close the opened socket while when exiting the application using SystemNavigator.pop
(or using back button) the process is kept running and opened socket will never been close. Application will not being able to bind to this port ever again without being manually killed.
In other words, it means that for a given process, we can have multiple initState
called for the same widget in the tree that are not matched by a call to dispose
. I can hear that it is not the exact same widget, but where did the previous one go then ? The unbalance between initState
/dispose
calls is a big sign that there is something wrong here.
@Hixie if I made some bad assumptions, please correct me and points us to documentation that define how to handle these cases properly.
But for me, there is either a lack of documentation in this area or there are inconsistencies in how dispose
are called.
As mentioned earlier by @maheshmnj #53997 is a very good example of what can go south when initState
is not matched by a dispose
.
In Android, contrary to what you seem to believe, the process is not stopped when going out of the last activity. The activity is destroyed but the process is still running.
It ends up with this buggy behaviour where some code may still be running but end up trying to reach a dead Flutter engine, which cannot be handled by dart code. The reporter needs to implement this hacky workaround to put a blank route at the root so that going back from the Widget which needs cleaning has its dispose
method actually called. This blank route is only there to then stop the app by calling SystemNavigator.pop
.
I cannot see how this can't be considered as a bug from the flutter engine, but if the problem lies in our usage of Widget, please let us know how we can properly handle these cases for all your tier-1 platforms.
The Android issue will probably get resolved when @goderbauer's multiwindow work lands (or some time after it lands, once we remember to hook up Android to it!).
Let consider the following example of an application that binds to a TCP port when opening a Widget and closes it when widget is popped out of the tree.
Does adding runApp(SizedBox.shrink)
just before the SystemNavigator.pop()
call do what you want?
Does adding
runApp(SizedBox.shrink)
just before theSystemNavigator.pop()
call do what you want?
Not really, I need then to comment SystemNavigator.pop()
for dispose
to be called.
I get your point that we could always use some alternative to effectively call our clean-up code when we want to exit the app programmatically. The problem is that the exit intent can come from other sources and each platform has its own specific sources (back button on Android, window close button on desktop, close tab in web,...).
While flutter should provide the common abstraction layer, it does not for this specific case and it adds a burden to the app developer which is likely to think dispose
is the function to use for releasing what is acquired in initState
by reading official docs and guides. And the expectation from developers would be even stronger given the fact that this is the actual behavior for widget tree changes outside flutter engine termination.
The Android issue will probably get resolved when @goderbauer's multiwindow work lands (or some time after it lands, once we remember to hook up Android to it!).
That's encouraging :)
How could we track the progress of it ? Is it https://github.com/goderbauer/mvp ? It seems desktop-oriented, so shouldn't it fix it for linux platform as well ?
Why are some widget state dispose() executed when the page is closed, some widget states don't execute dispose, it's really annoying, I want to cry, and the resource release is a problem.