Flutter: Instance state not saved when app is killed by OS

Created on 12 Nov 2016  ·  139Comments  ·  Source: flutter/flutter

What is instance state, and why it exists

On Android, an Activity can be killed at any time by the system. This happens usually when Android needs memory when your Activity is not in the foreground, or because of a non-handled configuration change, such as a locale change.
To avoid the user having to restart what he did from scratch when Android killed the Activity, the system calls onSaveInstanceState(…) when the Activity is paused, where the app is supposed to save it's data in a Bundle, and passes the saved bundle in both onCreate(…) and onRestoreInstanceState(…) when the task is resumed if the activity has been killed by the system.

The issue about it in flutter

In the flutter apps I tried (Flutter Gallery, and the base project with the FAB tap counter), if I open enough apps to make Android kill the flutter app's Activity, all the state is lost when I come back to the flutter activity (while not having remove the task from recents).

Steps to Reproduce

  1. Install Flutter Gallery
  2. Open the device's settings, and in developer options, switch "Don't keep activities" on. (see the screenshot)
    dev_option
    This will allow to simulate when Android kills Activities because it lacks memory and you're not in the foreground.
  3. Open the Flutter Gallery app and go anywhere other than the main screen.
  4. Go to the launcher by pressing the device's home button.
  5. Press the overview button and return to Flutter Gallery. Here's the bug.

What's expected: The app is in the same state that where we left off, with untouched UI.
What happens: The activity is restarted from scratch, losing all the UI state, even really really long forms.

P2 annoyance crowd routes framework new feature

Most helpful comment

Quick update: I am currently actively working on exploring solutions for this problem.

All 139 comments

@eseidelGoogle That's right. In which format could the flutter Activity state be saved, if it can be at all as of the current version?

Right now we don't do anything to save anything.

The framework itself has very little state worth saving -- it's all animation and stuff like that -- so it may be that we always leave this up to the app to do. We should probably expose it at the Dart level though. (Right now it's only exposed at the Java level.)

@Hixie On Android, all framework Views have their state automatically saved and restored by the system, which only requires the developer to save manually the non UI part of the instance state. Shouldn't flutter work the same way for all UI widgets to prevent developers from having to write all the boilerplate each time to save where the user was, the scroll position, the enabled state of some button, the state of the previous screen and so on…?

In Flutter, there's very little framework state to save. For example, the enabled state of a button is not state, it's input provided by the application. The current route history is stored in the framework, but not stored in a state that the framework can rebuild (since it's all instances of objects provided by the application code). The scroll position is about the only thing we could actually store (and we do save that in a store currently, just not one that survives the app).

But in any case we should definitely do better than today.

As an experienced Android Developer, I'd like to take part to the conversation with iOS developers too when it'll be discussed so flutter can be the perfect framework to build iOS and Android apps.
I think the persistence ability of the framework may be important to save data, preferences, cache data and states in the most developer friendly possible way.

Whoa I didn't realise this was the case? This is actually critical, and it's worth mentioning that on devices with less memory the process could be killed quite easily!

What about saving the PageStore (or related) as a byte stream through serialization.dart in onSaveInstanceState?

@Takhion Could you link "PageStore" you're mentioning? I'm interested in trying to workaround this issue since it seems it won't be fixed any soon (31st december of 2029 is a liiittle far IMHO)

@LouisCAD sure it's here: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/page_storage.dart

I think you'll need to save more than that though: at least the current route(s) and maybe some state?
What do you think @Hixie?

We don't currently do anything to make this easy. We haven't studied this problem in detail yet. For now I recommend storing the information you want to persist manually, and applying it afresh when the app is restored.

Do we expose the necessary lifecycle events ("you're about to be killed!", "congrats, you're now restored") into Dart code, so developers can handle persisting state? That seems like the sufficient capabilities to unlock devs to explore solutions. Thoughts?

@sethladd Just exposing it to developers is not enough IMHO. Flutter needs to save UI state too, without developers having to do it manually repeatedly for each app with dirty and hardly maintainable verbose boilerplate code.

@LouisCAD thanks for the feedback. I'm thinking in steps... what's step one? Do we have the lifecycle events exposed yet?

@sethladd all I could find is this, which doesn't expose any callback for saving/restoring instance state

@Hixie you mentioned over in #3427 that we do have hooks for lifecycle. Did you mean https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html ?

Is Flutter using multiple Activities by default? (e.g. if you go to a new screen).

Process death is fairly common, the user hits the Home button and launches some other memory/CPU intensive application and your application will be killed in the background (or your application has been in the background for too long). It's an integral part of Android OS - every screen (Activity) should be able to persist and restore its' state and the process can be killed at any time after onStop().

Android will recreate the backstack of Activities if the user returns to a killed app, the top Activity is created first and then activities in the backstack are recreated on-demand if you go back in the backstack history. This can be a bigger issue in case Flutter is using multiple Activities (not sure if it does).

This means that if you don't have state saving implemented then the system will recreate the Activities but everything else is lost (process was killed), thus leading to inconsistency and crashes.

I wrote an article about process death on Android https://medium.com/inloop/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

Also there is no (clean) way to prevent Android from killing your application once it's past the onStop() state (after clicking Home or if the app is just not the current foreground app). So somehow you have to deal with it. Default Android widgets are saving their state (e.g. entered text in EditText) automatically into the Bundle instance. Your Activity will notify you that it's necessary to store your state by calling onSaveInstanceState(Bundle). So maybe Flutter should be able to forward this onSaveInstanceState callback from the Activity to your "screens". You will get the Bundle with the saved state back in the onCreate(Bundle savedInstanceState) or onRestoreInstanceState(Bundle savedInstanceState) lifecycle callback in your activity.

So to recap - Flutter could maybe forward the onSaveInstanceState() and onRestoreInstanceState() callbacks to the developer. Ideally you would also wrap the Android Bundle object into something that can be also used in Flutter. The next step would be that all Flutter widgets inside the screen would be also notified about these callbacks and use them to persist their current state.
The Android OS will then take the Bundle and actually persist it on disk so that it's not lost in case the process is killed and can be deserialized again.

Good luck with that :-). Please take care and don't introduce too much of this Android state / lifecycle hell to Flutter.

I am not sure how that works on iOS - but I think there is something similar but it's not "necessary" to use it (?).

A lifecycle callback would be fine for me. I would just store/load the serialized redux state.

Thanks for all the feedback! @Takhion also offered to help here. Perhaps an API design and a library is a good start? If that library works, we can look to integrate more formally. Also, the library will help identify what we need to do at the low-level engine (if anything). Basically: what's the concrete API proposal?

Is Flutter using multiple Activities by default? (e.g. if you go to a new screen)

@DanielNovak Flutter uses it's own "routing" system and by default it integrates with Android through a single View in a single Activity. You can have a hybrid Android/Flutter app and as such potentially multiple Activities and Flutter Views, but in that case you could easily save/restore instance state through Android directly.

I would just store/load the serialized redux state

@zoechi you probably don't want to do that because the saved instance state Bundle has to go through IPC with a hard limit of 1MB for your entire Android app state. Instance state, by definition, should only be the pieces of data that would allow you to recreate the same conditions of whatever in-progress activity the user is performing, so: text input, scroll position, current page, etc. Anything else should either be persisted on disk or derived from other state.

Flutter exposes the Android onPause lifecycle event. The Android onDestroy() lifecycle event is not exposed. I guess the right approach would be to hook into the onPause() event for storing instance related state.
To be compatible with Android's onSaveInstanceState() and onRestoreInstanceState(), maybe it makes sense to add similar methods to Flutter widgets.
@sethladd @LouisCAD Did anyone create a design proposal?

@raju-bitter onPause() is not optimal, it's better to hook into onSaveInstanceState(). Here is the javadoc from onSaveInstanceState which mentions that onPause() is called more regularly than onSaveInstanceState (you would be triggering state saving more often than necessary or when it's not necessary at all):

Do not confuse this method with activity lifecycle callbacks such as onPause(), which is always called when an activity is being placed in the background or on its way to destruction, or onStop() which is called before destruction. One example of when onPause() and onStop() is called and not this method is when a user navigates back from activity B to activity A: there is no need to call onSaveInstanceState(Bundle) on B because that particular instance will never be restored, so the system avoids calling it. An example when onPause() is called and not onSaveInstanceState(Bundle) is when activity B is launched in front of activity A: the system may avoid calling onSaveInstanceState(Bundle) on activity A if it isn't killed during the lifetime of B since the state of the user interface of A will stay intact.

@Takhion

instance state Bundle has to go through IPC with a hard limit of 1MB for your entire Android app state

That's not my issue.
I just need to know when to persist/restore, persisting myself on disk is fine for me.

Propagating the onSaveInstanceState/onRestoreInstanceState is the easy part: Flutter is contained in a View and has proper access to those callback. What I'm trying to do is to create a design proposal and a prototype to actually automatically serialize objects contained in a certain "repository", so that using it would become painless (as opposed to having to do everything manually).

@Takhion you are the wind beneath my wings. Can't wait to see the proposal!

OK so I'm building a PR for this and I hit a roadblock: [View.onSaveInstanceState] (or [Activity.onSaveInstanceState], for the matter) require to provide the saved instance state synchronously, and from my experience (and this [comment]) it looks like host/platform messages can only be async. I suspect this is because they use Android's message queue.

@sethladd @Hixie @eseidelGoogle @jason-simmons please tell me there's a simple way to make a single synchronous/blocking call from Android to Flutter that doesn't involve ad-hoc C/C++ code, since [dart:jni has been retired]?

Cc @mravn-google

The asynchronous nature of platform messages comes from the fact that the Dart UI executes on a different thread than that used for the Android platform callbacks. But you can always turn an asynchronous API call into a synchronous one by using a latch:

final MethodChannel channel = new MethodChannel(messenger, someName);
final CountDownLatch latch = new CountDownLatch(1);
channel.invokeMethod("someMethod", someArguments, new MethodChannel.Result() {
  @Override
  public void success(Object result) {
    // handle successful invocation
    latch.countDown();
  }
  @Override
  public void error(String errorCode, String errorMessage, Object errorDetails) {
    // handle failed invocation
    latch.countDown();
  }
  @Override
  public void notImplemented() {
    // handle invocation of unimplemented method
    latch.countDown();
  }
});
try {
  latch.await();
} catch (InterruptedException e) {
  // handle interruption
}

You can declare a final AtomicReference<SomeType> variable (or similar) next to the latch, if you need to convey values from the // handle xxx locations to the code that runs after latch.await() returns.

thanks @mravn-google !

@mravn-google that's exactly the first approach I tried, but the main thread on Android just hangs on the await() call forever :(

@Takhion Right, of course.

you can always turn an asynchronous API call into a synchronous one by using a latch

... unless the async response is to be delivered to the thread you are already on :-/

We seem, then, to need some sort of sync handshake for onSaveInstanceState. Do we know of other, similar callbacks that require a sync answer involving Dart-side computations? That would call for a general purpose, synchronous platform message exchange. If not, we could do something specifically for onSaveInstanceState.

@mravn-google unfortunately Android is _full_ of these nasty "synchronous windows of opportunity", where everything needs to happen before returning from the method call! On the top of my head: many Activity lifecycle events, methods in Broadcast Receiver, SyncAdapter, etc. I'm honestly surprised it hasn't been a bigger issue before!

In my opinion, if we want Flutter to truly achieve feature parity with what's possible through "native" APIs, then having synchronous message communication is a must.

@mit-mit I meant SystemChannels.lifecycle and anything that uses that, yes.

@sethladd I think so.

@mravn-google thanks! I've closed #7560 in favor of this one, just to de-dupe and centralize the conversation.

I'm leaning towards adding a synchronous counterpart to MethodChannel for this kind of platform-to-Dart communication.

@mravn-google how is it going? Can I help?

@Takhion Thanks! There should be soon. I'm working on a synchronous method channel, and I hope to have a PR within another day or two. Once it's there I'll ask for your review. I want to hook the new channel up to onSaveInstanceState asap to test that everything works as intended in that scenario. If you already have an example app that you could contribute, that would be awesome.

@mravn-google awesome! I don't have an app because I am modifying the framework directly, but I can certainly provide a sample. Btw I'm using BinaryMessages directly since there's no need for a codec on the Android side (it's just bytes passed around), is that OK?

you guys rock.

@Takhion Direct use of BinaryMessages is just fine, if you have no need of a codec. I'll be adding a

void setSynchronousMessageHandler(String channel, ByteData handler(ByteData message))

to that class.

@Takhion Strike that. We cannot just make synchronous calls into the Dart VM to invoke the handler. So BinaryMessages will be unaltered. Instead, everything will be async from the point of view of the Dart code, as usual. The change will be made to BinaryMessenger and its implementations on the platform side. I'll add

ByteBuffer sendSynchronous(String channel, ByteBuffer message);

The platform view code supporting FlutterView will implement that by sending an async platform message and then wait on a latch. The response object included with that platform message will not post a reply-handling closure to the platform thread (which is now blocked), but will instead store the reply and release the latch.

@mravn-google works for me, as it doesn't stop us from sending back data from the Dart side, even if it's an async call :+1:
I guess there won't be any optimisations if one were to use Future.value(...) in the Dart async response?

@Takhion Please take a look at https://github.com/flutter/engine/pull/4358

I see you are mostly talking about Android platform here. But, actually, iOS also has somewhat similar mechanism to save/restore UI state when the app was killed for some reason. For example, when the user switches to a different app the current app is suspended and can be even killed because of memory constraints. You can find more info about this process in the official docs.

iOS developers expect this preserving/restoring procedure to be almost automatic. I guess, if your goal is to make truly cross-platform framework you should definitely take this into account.

You'd think something that intends to target Android as a platform at least attempts to obey the basics of the Activity contract....

@Zhuinden let's not complain about things that are still in beta? :)

Other synchronous api's are onLowMemory and onTrimMemory and onConfigurationChanged don't know if IOS also has similar api's or mechanisms

We may not want to reproduce the flawed android save&restoration mechanism on Flutter: You cannot save the whole state in the bundle because of the transaction size limit.

On android, you end up having multiple state save/restoration logics:

  • developer's savedInstanceState, but you can't store large objects inside, like a list of elements
  • database, where you can store large objects
  • most widgets also manage their own state
  • activity/fragment stack

It's really easy for developers to implement messy and buggy logic. In my own experience, it's really rare to see state save&restoration done right.

On flutter, the developer's state is the single source of truth.
Developers should persist this state into disk when it changes, without going trough the limited native platform mechanics.

Sure, you won't be able to easily save the scroll position, but who cares.
Most of the time, good UX for restoration means that the user should see the same screen than he was on previously. You can already do this with Flutter today.

Bottom point: there is room for improvement on the Flutter side in order to make state save/restoration less boilerplate (for things like Navigator). But I don't think it should provide hook to platform save/restore state.

Sure, you won't be able to easily save the scroll position, but who cares.

Everyone? I'd be really frustrated if the app resets its list everytime I accidentally trigger an orientation change. :p

@Saketme FWIW, Flutter does not reset scroll positions on orientation change.

Using a Flutter app with a bit of multitasking should be fun on an Android Go device! Progress lost everytime the 1GB of RAM or less are under pressure

@Saketme: as @mravn-google said, Flutter is not bound to configuration changes like android Activities.
The only state management needed is when app is killed by the OS while being in background.

@LouisCAD Sure state save&restoration is important.
Flutter developers can already implement most of this logic themselves, and Flutter should probably provide ways to make it less boilerplate. However the mechanism offered by Android is flawed and we could take the opportunity with Flutter to design something better, rather than hacking into Activity onSaveInstanceState / onRestoreInstanceState.

What's current the status of this issue?

Consider the case of a chat app, where I am typing a long message in the TextField.
Suddenly I receives a phone call and after some time, the OS kills the chat app due to low memory.
After the phone call, I come back to the chat app. Will Flutter automatically persist and recreate the message I was typing? or The app has to persist and recreate it manually?

Other than scroll position and route, what else are "states" that are not usually managed by developers?

@nsreenath Any chat app should persist messages draft by itself every few characters and when going to background. You may type a long message, and if the device shutdown for some reason, you'd not want to lose your progress on this. Android instance state is for light user input and progress into the screens

I actually slept on this and I came to the realization that it's actually quite easy for Flutter devs to detect if the application came back from process death or is completely new instance.

Very simple, as long as they check for savedInstanceState == null (assuming they put at least 1 item in the onSaveInstanceState(bundle)). If a static boolean flag isFirstInit == false and savedInstanceState != null, then it's after process death.

Then they can create any kind of serialization / deserialization callback for complex state which could be cleared automatically when they detect savedInstanceState == null. That way they only need to persist some random boolean, but can manage their own persistence system.

Very simple, as long as they check for savedInstanceState == null (assuming they put at least 1 item in the onSaveInstanceState(bundle)).

There is no savedInstanceState and onSaveInstanceState on iOS. But the app can still be killed by OS.

Consider a typical workflow:

  • A user goes to a specific screen inside the app
  • Then the user switches to another app
  • Suddenly the wise OS decides to kill the previous app to preserve the memory
  • The user returns to the previous app but she sees the home screen. Not the screen that was open when she decided to switch the app

So, what is currently the recommended way to preserve the screen history in Flutter?

P.S. Of course, this is not important if your app has just one screen :smirk:

@Zhuinden I fail to see many scenarios where that's needed anyway.

a) Chat example:
Always save and restore previous content written in the text field that was not sent.

b) Long form example:

  1. Always save the content
  2. When user comes back to the form screen, notify the user there is a draft version that was saved but not submitted, asking if he wants to restore it. It does not make any difference if user exited the form manually (maybe ask if user wants to save draft) or if the app was killed while in background.

There are probably some cases where specific knowledge of "fresh new screen" vs "new screen after process death" is useful, but that might not be so common when you design your app's content to be always backed by a database.

@storix You need to manually persist the current route and restore it when app launches again. Too much boilerplate for a common use case, I agree. But still doable.
Also: Many android devs would be surprised by how few iOS apps/devs bother with state restoration. Majority of devs I know is not even aware that's an issue. Guess it's the luxury of working on higher end devices only.

@Zhuinden the lifecycle callbacks from https://github.com/flutter/flutter/issues/6827#issuecomment-335160555 worked for my chat application. I just save some state every time when the app is switched to background. See also https://docs.flutter.io/flutter/dart-ui/AppLifecycleState-class.html

I would like to urge the Flutter team not to expose Android/iOS lifecycle methods as the canonical way of persisting/restoring UI state. Even in native apps, they were difficult to grok and code correctly, and the line between UI and application state was often blurry, with different mechanisms provided for each. It would be a shame to promulgate all of these disparate APIs into Flutter (especially if additional platforms are targeted in the future).

Flutter's stateless widgets have _already_ migrated the responsibility for UI state out of the widgets and into the app, so the problem of persisting/restoring UI state is increasingly covered by persisting/restoring _app_ state instead. I see this as a good thing, as opposed to treating UI and App state differently, as in iOS and Android.

I notice that some Flutter widgets, like ScrollView and TextField, provide "Controller" classes that encapsulate the dynamic state of the widget. In some ways they are acting as "memento" objects -- a snapshot of the widget's state -- which can be provided back to the widget at a later time to restore their state to some previous configuration. Sounds familiar. If these "mementos" were made easy to persist/restore, then an application could take responsibility for them as part of managing its own state. This would end the app/UI state dichotomy, would be opt-in, and would obviate the need for the Flutter framework to provide UI state preservation hooks (less is more!)

In other words, make it the developer's responsibility, but try to make it as easy as possible. If a developer thinks that a half-completed form is important enough for Flutter to persist across invocations, then one could argue that it's probably important enough to be considered application state. I realize that persisting the state of objects introduces versioning issues, but I think this is a more manageable problem.

Has there been any progress on this matter? I think there is a real need to distinguish between 'first run' and 'restored' process state. How else can you program to show the right screen when the user navigates back to a killed process?

@lukaspili you wrote: "The only state management needed is when app is killed by the OS while being in background."

Okey, then what about router stack, also save to disc? Also I dont find proper solution to determine that application was restored. Every time when app goes to background I need to save router stack, app data, state to disc? What if I dont use Redux?

I think as a first step, we should give plugins the possibility to save and restore state. Here is my proposal: #22328

Has there been an update on this issue?

I am facing the same issue. I have got a work around for saving text inputs here through Firebase realtime database. But the method is pretty crude and often lacks scalability option as every single user will be redirected to the same database. Hence my data is available to everyone else with an access to a gmail account. I have tried using Redis, but although I can create separate lists in the same database for other users, it is always done through a POST method. That means the view has to refreshed everytime a new data is stored. Which means no realtime visuals. A great glitch indeed.

There is a bigger issue here. I had some back-and-forth with @matthew-carroll two months ago and I'm quite confident this is underway.

I've started making a library that deals with this. I wouldn't actually recommend using it but it's one idea of a Dart-only implementation. #6225 made things a bit tricky.

I've noticed that the Google Ads app, reportedly being made with Flutter, does preserve navigation state after an activity restart. It would be a nice reference to know how is it implemented, even if it's a workaround.

@aletorrado Does the Google Ads app preserve the navigation state in case the user has discarded the app from the Android/IOS “task manager” and later on launched it again ?

@gitspeak No, it only preserves the state if the activity was automatically killed by the OS (maybe using the onSaveInstanceState hook).

@aletorrado So y’ep that’s probably the only way...

I actually tried to get into the Google Ads app but you need to have an active account to get to any screen beyond the very first one :D I can't test if it really works or not

It does. Guaranteed. It also preserves other state like scroll position.

To clarify some confusion:

  • Yes, Google Ads app is written with Flutter.
  • Being one of the early adopters, they had to do something unusual for background execution. They create and maintain their main isolate when the application starts (not the activity). Then, they pass this isolate to the Flutter view when it is created.
  • What you are observing as "state keeping" is a side effect of the methodology described above. When you kill the activity in Google Ads, the "UI" of Flutter does not die. It is kept in memory tied to the application context. When the activity is restarted, it loads the same isolate and viola, you get your UI back.

We don't recommend this approach for solving this problem because it does not solve the problem at all. If the application process dies, the entire state is still lost. You can try this with Google Ads by going and stopping the process from Settings > Apps. The UI will be lost.

There might be a way to save the isolate state locally but it needs more investigation because we are not sure how feasible/safe it is.

For now, the solutions described above are the best ones: Hook into native methods and selectively save the state of your app so you can restore it later. An framework supported automatic solution on both iOS and Android is still far away.

You can try this with Google Ads by going and stopping the process from Settings > Apps. The UI will be lost.

no that is totally ok. Force Stopping an app should lose the state.

Being terminated by low memory condition should not.

Either way, if they keep the state in application as a singleton in-memory without persisting anything to disk/bundle, then it WILL be lost.

It is a shame I cannot test the app without an account, as I won't create a Google Ads account just to tinker with the app itself. :thinking_face:

Hook into native methods and selectively save the state of your app so you can restore it later.

Indeed the best solution would be to persist the active state to disk, and detect process death on Android native side; if you detect process death, reload from disk, otherwise clear it and restart from scratch.

I don't know enough about Flutter's navigators to tell you if you can reconstruct the navigation history with some kind of setHistory or setBackstack method like you would with a backstack or Conductor or something like that.

Activities/Fragments handle this stuff internally with the ActivityRecord/FragmentRecord, so they are not mentioned beyond that.

Thank you for all those insights @mehmetf. It's sad to hear that support for saving/restoring state is far away from being implemented.

Maybe flutter_redux could help serializing some of the application state.

Well, if you persist the Redux store's blob onto disk. Just beware the "infinite loading dialog after process death" problem. :thinking_face:

To clarify some confusion:

Actually, I’m somewhat more confused..

Being one of the early adopters, they had to do something unusual for background execution. They create and maintain their main isolate when the application starts (not the activity). Then, they pass this isolate to the Flutter view when it is created.

How does that relate to the issue at hand ? Do Dart isolates run in a separate process ?

What you are observing as "state keeping" is a side effect of the methodology described above. When you kill the activity in Google Ads, the "UI" of Flutter does not die. It is kept in memory tied to the application context. When the activity is restarted, it loads the same isolate and viola, you get your UI back.

My understanding is that @aletorrado observed “state keeping” after the process was terminated, possibly when the app was moved to the background (a.g pressing the home button).

FYI: when an “Activity Dies” at the runtime will it is ALWAYS followed by terminating the process.

We don't recommend this approach for solving this problem because it does not solve the problem at all. If the application process dies, the entire state is still lost. You can try this with Google Ads by going and stopping the process from Settings > Apps. The UI will be lost.

As @Zhuinden mentioned this is not a valid "state keeping" scenario, so as discarding the app from the “task manager”

There might be a way to save the isolate state locally but it needs more investigation because we are not sure how feasible/safe it is.

That’s not the way to go about it. For Android, you should hook into onSaveInstanceState mechanism as it was made explicitly to save Activity state for those scenarios where "state keeping" is required post process termination. I'm not familiar with IOS app life-cycle.

For now, the solutions described above are the best ones: Hook into native methods and selectively save the state of your app so you can restore it later. An framework supported automatic solution on both iOS and Android is still far away.

No solution was described rather than acknowledging the appropriate Android facility for retaining state for those cases where the run-time decides to terminate the process.

No solution was described rather than acknowledging the appropriate Android facility for retaining state for those cases where the run-time decides to terminate the process.

I kinda did describe what we can do but I do not know enough about Flutter to tell you if it supports what is necessary for it (the ability to set up arbitrary navigation history at any given time 😄 )

@Zhuinden there are a "zillion" edge cases covered by onSaveInstanceState at is pertains to Activities/Controls ... trust me on this... but your welcome to investigate as an education exercise.

... and just to reiterate my position: Android Tasks/Activities, Services, Application Life-cycle (including onSaveInstanceState) are all part of the Android Programing Contract “incidentally" they are first realized in Java but the key takeaway is that they are means of imposing system-level constraints on the App Process and since Flutter code lives within an Android App Process it would be unwise of Flutter to bypass these constraints.

Dear Android developers,

thanks for your passion in delivering the best user experience to your users by saving state not only across configuration changes but also during process death. You might have read in the 200 comments above that Flutter is struggling when it comes to saving application state. I'd like to tell you why and how to solve it.

Why can't I use onSavedInstanceState?

onSavedInstanceState requires developers to synchronously save app state. You have to return immediately. But communication to Flutter is asynchronous by default (Future). Therefore you can't request the saved state from your Flutter app when Android asks for it.
_But you could return the saved state if you'd have created it beforehand, couldn't you?_

The problem is not that onSavedInstanceState is inaccessible, the problem is the timing. When Android ask to save the state, you don't have to data.

Recovery solution A: Your own saved instance state file

You could listen to onSavedInstanceState and inform your flutter app about this event. Then save your app state and write it in a file. When your app gets recovered from process death (you can detect this via savedInstanceState on the Android side) load the file from file system, parse it and recover your state. (Hint: Use platform channels, they're easier than you might think).

You can even recover state when users killed the app, or dismiss the saved state if it is way too old!

Recovery solution B: Continuously save state

We could run a timer and continuously save the app state on the platform side in memory. Let's say every 3 seconds. When Android then calls onSavedInstanceState we can return the previously saved state. (Yes, we might lose changes happened in the last 3s)

Recovery solution C: Save state at key points

Like solution B the saved state will be kept in memory on the platform side. But instead of saving the state every few seconds, you manually request to save the state after users did some actions. (opened a drawer, entered text in a TextField, you name it).


All 3 solutions are combinable, use what fits best to your app.

But how can I save my app state?

Unlike Android Views, Widgets don't have a onSaveInstanceState callback. The Widget tree is not saved automatically. And that's a good thing! UI and state are finally separated and there's no reflection magic when restoring View state or instantiating Activities and Fragments.

To save state you have to iterate over each screen in your app and save all information which is required to restore them. I like to do this in json but you might choose protobuf for better performance (Measure before you optimize!).

Looking at our beloved sample counter app this can be super easy:

// complete app state of sample application
{
  "counter": 24,
}

If you have multiple screen or dialogs you might end up with following app state json:

{
    "currentRoute": "/todo-detail/241",
    "routes": {
        "todo-detail/241": {
            "edited": "true",
            "title": "Buy bananas",
            "picture": "http://....",
            "show_confirm_delete_dialog": "false"
        },
        "todo-list": {
            "current_scroll_position": "365",
            "filter": "Filter.TODAY"
        }
    }
}

The actual implementation depends heavily on your app architecture.

  • If you're using Redux you could use a middleware to persist the global app state on the fly.
  • If you're using scoped_model it might be enough to save the models. Recreating them is fundamentally different than recreating a Redux state.

The best tips I can give you:

  • Keep a strict separation of your UI and your models.
  • Look at each screen, and ask yourself what you really need to recover state. Most of the time it's really not much.
  • Talk to your iOS colleagues and they will tell you they haven't implemented state restoration via encodeRestorableState in years. Android 1M+ vs iOS 974

Pascal

Turn on "Don't Keep Activities" setting.

No, this does not properly test against process death. This does not happen EVER unless you explicitly enable that setting. 😞

If you have multiple screen or dialogs you might end up with following app state json:

{
  "currentRoute": "/todo-detail/241",
  "routes": {
      "todo-detail/241": {
          "edited": "true",
          "title": "Buy bananas",
          "picture": "http://....",
          "show_confirm_delete_dialog": "false"
      },
      "todo-list": {
          "current_scroll_position": "365",
          "filter": "Filter.TODAY"
      }
  }
 }

Oh boy, we're reaching the "navigation state is part of application state as the state of a finite state machine" for this to work, but I like the way it looks 😄

Oh boy, we're reaching the "navigation state is part of application state as the state of a finite state machine" for this to work

😂 don't get me started.

The rest of the app state should be saved in a database/on the server. The only transient state worth saving in onSaveInstanceState is the navigation stack and some user inputs. That's also what most people are asking for in this thread.

The rest of the app state should be saved in a database/on the server. The only transient state worth saving in onSaveInstanceState is the navigation stack and some user inputs. That's also what most people are asking for in this thread.

and thats KEY! (as well as “Android Service” support)

@gitspeak, the last exchange we had have nothing to do with this issue. I will hide both. If you wish to continue the discussion, please PM me using my github handle @ gmail.com. There's clearly a misunderstanding here that's beyond the scope of this issue.

As one of the developers working on the interface between Flutter and Android, I'd like to thank @passsy for providing a great overview of the issues involved with saving state.

I would also like to add that I'm interested in understanding any specific use-cases that developers in this thread are dealing with. Saving state is a concept that likely spans many different goals. What one app calls state, another does not. One app absolutely must have guaranteed saved state, while another would simply prefer to save some state but won't break without it. Some apps are entirely built with Flutter, other apps must synchronize information between traditional Android/iOS UI and Flutter.

If any of you would like to describe the particular use-case that your app needs to support with regard to saving state, please feel free to email me at [email protected] - I am actively assembling use-cases.

@passsy Please consider my comments below as a sincere attempt to provide constructive feedback

Recovery solution A: Your own saved instance state file
You could listen to onSavedInstanceState and inform your flutter app about this event. Then save your app state and write it in a file. When your app gets recovered from process death (you can detect this via savedInstanceState on the Android side) load the file from file system, parse it and recover your state. (Hint: Use platform channels, they're easier than you might think).
You can even recover state when users killed the app, or dismiss the saved state if it is way too old!

Several issues:

1) There is an inherent race-condition with this approach. The "state persisting" thread may very well outlive the Android app Main thread which invokes the life-cycle events, in which case, the process may be terminated while the file is being written. You may attempt to coordinate the two threads by blocking in a life-cycle method on a completion signal from the “state saving” thread but that, on top of introducing additional synchronization complexity, cannot assure correctness since a long enough delay can introduce ANR. (https://developer.android.com/topic/performance/vitals/anr)
You might argue that "state saving" to file is so quick that this would unlikely happen but consider actual delays in thread scheduling and storage latency while other processes are running such as background downloads, play store updates, media player, etc… I'm not into debating this, but my personal experience (shared by others) is that apparently Android devices are not immune to the same "flu" infecting Windows PC’s - they also get slower over time. I just recently wiped clean my Nexus 7 and not only it took HOURS to download and install all apps over Wifi, but the device responsiveness is now a complete joke to point where I doubt having patience using it even for looking up recipes.

2) You can't have a reliable definition of "way too old". If I discard the App from the task manager, I expect all state to vanish immediately, while if I switch to another app I expect the state be preserved indefinitely so long as the phone is on. Point being, Android doesn’t provide you a way to distinguish between these two states.

3) Not all UI controls expose their state through an API which allows the user to configure the control for a particular state. Such API would undoubtedly incurs additional development effort on behalf of the control developer. On android well behaved UI controls are expected to abstract "state-preserving" issues from the developer by handling onSave/Restore InstanceState notifications internally thereby controlling what state if any gets preserved. If I recall correctly, the AutoComplete control retains the query term but not the result list.

Recovery solution B: Continuously save state
We could run a timer and continuously save the app state on the platform side in memory. Let's say every 3 seconds. When Android then calls onSavedInstanceState we can return the previously saved state. (Yes, we might lose changes happened in the last 3s)

Very much disagree, besides having the same issues surrounding "Solution A", it adds a new risk of opening a can of data duplication worms.

Recovery solution C: Save state at key points
Like solution B the saved state will be kept in memory on the platform side. But instead of saving the state every few seconds, you manually request to save the state after users did some actions. (opened a drawer, entered text in a TextField, you name it).

Having same issues surrounding "Solution A".

The Widget tree is not saved automatically. And that's a good thing! UI and state are finally separated and there's no reflection magic when restoring View state or instantiating Activities and Fragments.

Disagree. Android's automatic retention of the Widget Tree in those cases where it's state is subject to being tossed is very helpful! Why on earth would you want to move this responsibility in it's entire to the app developer?? Why does this stand in contrast to UI-state separation ?? Actually, this automatic way of saving / restoring the Widget Tree seems like a perfect example of UI/State separation..

The "Solution A" seems perfectly plausible to me when using appropiate locks, and no ANR should be triggered if the amount of work is reasonable. Also, by embracing the platform's notification mechanism it avoids doing unnecessary serialization work all the time, which may be expensive to do for every single keystroke.

About the possibility to automate this behavior, it seems clear to me that having no reflection mechanism in the Dart runtime, and having no structured way to store the state in memory in Flutter, that's not possible by any means. At the moment, the simplest approach I can think of doing this would be to opt-in to keep state in a single dynamic object (just like this.props and this.state on React).

@aletorrado

The "Solution A" seems perfectly plausible to me when using appropiate locks, and no ANR should be triggered if the amount of work is reasonable.

I agree. when ... Where the Android Model is far more simple and robust since it does not require the developer to deal with locks, it does not persist transient state to disk and it retains state only for those cases where it's relevant something you (flutter) can't do without hooking into the Android lifecycle events and the onSaveInstance facility - to begin with, so what is it good for?

Also, by embracing the platform's notification mechanism it avoids doing unnecessary serialization work all the time, which may be expensive to do for every single keystroke.

There is no requirement to persist every key stroke but rather a snapshot consisting of selected states

The data should just be stored in the Bundle as a ByteArray to avoid
triggering any ANR or lag caused by I/O on main thread.

On Wed, Dec 19, 2018, 6:08 PM Alejandro Torrado notifications@github.com
wrote:

The "Solution A" seems perfectly plausible to me when using appropiate
locks, and no ANR should be triggered if the amount of work is reasonable.
Also, by embracing the platform's notification mechanism it avoids doing
unnecessary serialization work all the time, which may be expensive to do
for every single keystroke.

About the possibility to automate this behavior, it seems clear to me that
having no reflection mechanism in the Dart runtime, and having no
structured way to store the state in memory in Flutter, that's not possible
by any means. At the moment, the simplest approach I can think of doing
this would be to opt-in to keep state in a single dynamic object (just like
this.props and this.state on React).


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448671264,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGpvBUQk17YOsjLMHTWcdJHL9rR71u6lks5u6nKGgaJpZM4KwSuX
.

The real trickery is building up this ByteArray you speak of :slightly_smiling_face:

I'd rather say that the real trickery is to be able to restore this
ByteArray properly.

On Wed, Dec 19, 2018, 6:41 PM Gabor Varadi notifications@github.com wrote:

The real trickery is building up this ByteArray you speak of 🙂


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448682229,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGpvBep8AW2xkKECt6uyTjqRGwri9Pmoks5u6npKgaJpZM4KwSuX
.

No. Once you have the byte array, it is very easy. Just show a loading indicator while you're loading it (assuming you've told yourself with channels that you need to load it).

The real issue is Flutter integration, what I call the restore part.
Navigation should be saved automatically, and user input too, just like on
native Android apps.

Because of this issue, Flutter apps can't integrate correctly in Android as
they don't behave correctly when there's RAM pressure and the user switches
apps in these conditions before coming back.

On Thu, Dec 20, 2018, 2:42 AM Gabor Varadi notifications@github.com wrote:

No. Once you have the byte array, it is very easy. Just show a loading
indicator while you're loading it (assuming you've told yourself with
channels that you need to load it).


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448827537,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGpvBR4rmSxelodAjZvcQ6kOwPd7yxKPks5u6ushgaJpZM4KwSuX
.

A bunch of us discussed this a bit more here: https://github.com/mehmetf/flutter/issues/1. Relevant points to this issue:

  • To correct (https://github.com/flutter/flutter/issues/6827#issuecomment-447488177): Google Ads was tested with "Don't Keep Activities" turned on. The app process does not die in that case so my explanation at (https://github.com/flutter/flutter/issues/6827#issuecomment-447955562) stands. As @gitspeak and @Zhuinden have pointed out, this is not a good way to test for the scenarios we are guarding against as it does not work that way in practice.

  • Whenever the application is backgrounded, View.onSaveInstanceState is called on the UI thread for every View in the hierarchy which has save enabled. So, a good solution from developer's point of view would be for FlutterView to implement this. To prevent race conditions with the UI thread (which you can't/shouldn't block), this method should not reach into the Flutter application as there's no synchronous way to do it.

  • Whatever onSaveInstanceState saves cannot be larger than 1MB so saving the entire isolate state is out of question. Instead, we can possibly come up with a way to split the code, assets and data that is contained in the isolate memory and dump only the data portion serialized into a parcelable.

  • When the view is later restored, FlutterView creates an isolate and unpacks the previously saved data portion if any.

  • When the app is backgrounded, the state should presumably stop changing on the Flutter side. There will always be corner cases so coming up with a list of best practices for Flutter apps would be nice to accompany this solution (considering both lifecycle and memory pressure).

  • I would imagine this would be a far more pressing issue when it comes to emerging markets such as India where Flutter is expected to run on low-end phones. Android Go is also rumored to have a far more aggressive memory management strategy.

  • They create and maintain their main isolate when the _application_ starts (not the activity). Then, they pass this isolate to the Flutter view when it is created.

can you provide an example this code?

They create and maintain their main isolate when the application starts (not the activity). Then, they pass this isolate to the Flutter view when it is created.

can you provide an example this code?

I can't provide a copy/paste type of example but here's what's happening:

Application

  • In your Application.onCreate, call initialization functions for FlutterMain. (startInitialization and ensureInitializationComplete). You can find references to these in the flutter/engine repo.
  • Create a new FlutterNativeView object, pass in the Application instance as context.
  • Run your bundle in this native view (Look for examples of runFromBundle).
  • Implement an interface in this Application that lets you return this native view (something like FlutterNativeViewProvider).

Activity

  • Extend FlutterFragmentActivity and override these two methods:
  @Override
  public FlutterNativeView createFlutterNativeView() {
    FlutterNativeViewProvider provider = (FlutterNativeViewProvider) getApplication();
    return provider.getFlutterNativeView();
  }

  @Override
  public boolean retainFlutterNativeView() {
    return true;
  }

Something along these lines should get you going. Note that your Flutter application will start running before an Activity (and thus a window) is available. Therefore you should not be calling runApp() in your main function until you receive a signal from Activity that it is ready. You can do that via PlatformChannels and one place to signal that would be in createFlutterNativeView().

Disclaimer(s)

  1. As I mentioned above, we don't quite condone this pattern since it does not really solve this particular problem and it is quite messy.

  2. Note that many of these APIs are slated to change. Since add2app use cases are still evolving, the Android and iOS embedding APIs are not quite stable yet. We will announce breaking changes on flutter-dev@ as usual.

@passsy, @gitspeak

Recovery solution A: Your own saved instance state file

To be sure that you have a valid state-file you can write to a .tmp file and then rename when writing is done. If you also keep the previous state file, you always will end up with valid state. Also when the process is killed while saving the state.

@jsroest

you always will end up with valid state.

But not necessarily the correct state, which is kind of the whole point..

There is also no requirement for persisting state to non-volatile memory to begin with ..

@gitspeak

you always will end up with valid state.

But not necessarily the correct state, which is kind of the whole point..

There is also no requirement for persisting state to non-volatile memory to begin with ..

In many situations having a consistent state is more important than having 'most of the latest values'. Relational databases used with transactions are an example of that. Which in my opinion is also a great place to store state.

Persisting to non-volatile memory has some advantages; The process is also killed on 'phone drops', battery changes, hardware failures, os updates, reboots in general etc, so the state does persist when saved to non-volatile memory.

@jsroest Your arguments are valid but IMHO not applicable to this particular issue since the "state" being alluded to here is transient UI state (e.g Scroll position, active selection(s), temporary text input, etc...). Keep in mind that restoring this type of data is only required in those situations where the user has not explicitly discarded the "Screen" but user-input can still get lost, like when a user switches to another app. There is no user-expectation to recover transient state upon power-loss much like there is no user expectation that the browser will scroll the last displayed webpage to the exact offset prior to a system power loss. Point being, the nature of the data considered to be "transient state" is significant and the discussion here (Android onSaveInsatnceState facility in particular) is about handling that type of data.

@gitspeak
I write programs for the enterprise. For example programs that are used in warehouses by order pickers, or other example in the retail business, where the shop owner scans items to order from his supplier. My customers expect the application to start where they left it, no matter what happened. That includes transient state, but also the navigation-stack. Could you explain why this is not a perfect fit for the OnSaveInstanceState facility as discussed here? Maybe the user expectation in the consumer market is different, but the same logic to save and restore state can be used IMHO.

@jsroest

My customers expect the application to start where they left it, no matter what happened.

Sorry, I don't know how to build applications for such customers, perhaps someone else can help ...

@jsroest That's a great question but is not within the scope of this issue. Flutter is limited by what the platform itself can do and, as @gitspeak mentions, Android's onSaveInstanceState is about saving transient UI state where the data lifecycle is different than what you want. Local storage or remote storage (if you want the state to survive across uninstalls and devices) would give you what you want but it has caveats, which are again outside the scope of this issue.

Further recommended reading:

https://developer.android.com/topic/libraries/architecture/saving-states [Note for instance how this article separates local storage from onSaveInstanceState]
https://developer.android.com/guide/topics/data/data-storage
https://firebase.google.com/docs/firestore/

@jsroest if your customers expect the app to return where it was even if it was force-stopped, task-cleared or the OS was restarted, then that is something completely custom, and not what people are talking about here.

onSaveInstanceState was saving the task state for a given task, but it was dropped on task clear or force stop or OS reboot.

@mehmetf I think this issue has indeed reached a level of verbosity at which it can be quite challenging to grok. With @LouisCAD blessing, I suggest you close this issue and open a new one which starts with your most recent summary (perhaps somewhat adapted to emphasis the problem statement and scope) along with back references to previous discussions.

I concur but I will leave that up to @matthew-carroll; he will likely own this issue.

Thanks for the feedback.

I don’t want to push things, but I think I was not clear enough describing what I am looking for. I am not up to par with all the terminology used here on this thread. I see a lot of similarity between what I would like to see in Flutter and what @LouisCAD and @passsy are describing.

My programs usually consist of the following parts:

  • Backend for pushing data from the steady state when the user is done with a job.
  • Local sqlite database for the steady state
  • In memory data-structure called ‘variables’ for the transient state that can be serialized to non-volatile memory so that it can outlive the process.

The ‘variables’ data-structure only has simple classes with properties that are serializable. An important part of this ‘variables’ datastructure is the screenstack. (Simply List). The screen that’s on top, is the last screen the user interacted with. The one below is the one that the user navigates to when pressing 'back'.

I can imagine that the OnSaveInstanceState can be used on the Android platform to trigger a serialization of this datastructure. Something similar has to be found on the other platforms (iOS, future: win, mac, web), but as @passy suggests other key points also may trigger this and that may be good enough.

When the program starts, it will check if a serialized datastructure is available and if the current version of the program is the same as the version that serialized it. This can be done just by comparing version numbers. We do not want to load a datastructure that is not compatible.

If this all adds up then the datastructure is deserialized and available in memory. The program loads the screen that is on top of the stack. The screen itself loads its transient state from the datastructure. Now we have a program that survives process deaths. (Or am I missing something? This definitely works on my past projects on windows mobile, Xamarin forms and asp.net. I think it should also work in Flutter).

Sample application, where SXXX stands for a screen with a number. The number is only used to remind the programmer which screens belongs more or less together.

S100 Main menu
  S200 Order pick
    S210 Select order
      S220 Confirm pick location
        S230 Pick nr of pieces
          S240 Report shortage
        S250 Scan dropoff location
    S300 Cycle count
      S210 XXXXXX
        S220 XXXXXX
      S230 XXXXXX
    S300 Reprint labels
      S310 XXXXXX

Sample variables datastructure

Class variables {
    Class AmbientVariables {
        ….
    }

    Class ScreenStack{
        List<string> ScreenStack;
    }

    Class Orderpick {
        int selected_order;
        string comfirmed_location;
        string nr_picked;
        ….
    }

    Class CycleCount {
        ….
    }

    Class ReprintLabels {
        ….
    }
}

All that I would like to see in flutter is probably already there, except the recreation of the screenstack aka navigationstack. I have no idea how to recreate this object tree in memory. I am also not sure if I should want to recreate it. I can also make a navigation stack of my own and implement this in flutter as a single page application. The same as I have done in previous projects. But then I would loose a lot of built-in goodies from the MaterialApp and Scaffold classes, where the back arrow/button is the most obvious one.

I realize that this does not automatically save all transient state. For example, selected texts, position of lists, position of a specific animation. But as the programmer, you can decide per screen what is needed to save. Although that is exactly what @LouisCAD is trying to prevent because all of the boilerplate code needed.

Should I make a new issue or does this fit into this thread?

Thanks again for the feedback, I really appreciate it.

@jsroest Thank you for the detailed explanation. Please post this on StackOverflow with the flutter tag. You are essentially asking for a recommendation on how to structure and build your routes and how to save the last visited route in the persistent storage.

You might think that solving this particular issue solves your problem too but that's not true. You can PM me at my github handle @ gmail.com if you don't understand why.

Any progress on this issue?

@vanlooverenkoen theoretically if you persist the navigator's route list AND the key hierarchy to a file (and the state of stateful widgets), and clear it out when on Android native side you get savedInstanceState == null, then you could potentially make it persistable and restorable, it is just not that easy.

@Zhuinden It's actually a lot more complex that just saving the routes and keys. Communication between screens is either done using callbacks or by awaiting a result. You would somehow have to restore these states as well. Otherwise you end up having something that looks correct but does not act as such. You could get around restoring callbacks and such by using a form of communication using data bundles just as you do using native Android (with intents), but that would take away a lot of Flutter's ease of use. As long as there is no real solution to this problem, Flutter is not usable for a lot of apps, mainly those that are used to collect large and complex form-based data. I wonder why this problem is not openly addressed by the Flutter team, because they surely must be aware of it. Maybe it would reveal a serious flaw in Flutter's architecture and show that, beyond flashy presentations to impress management, the system is not a good option for real-life applications.

@jsroest @mehmetf @claudiofelber, does the answer to this StackOverflow question address these issues? https://stackoverflow.com/questions/54015775/what-is-the-best-way-to-pass-data-or-arguments-between-flutter-app-screens/54015932

I've been following this from the beginning, but had to re-read a bunch of messages as I lost context.

If the Flutter team believes there should be a new, fresh issue, with less flood and clearer plans, I have no problem with that and this one being closed as a result.

I've created the native_state plugin to leverage the Android and iOS mechanisms for saving app state when the process is killed.

The Navigator can already restore the app routes through the initialRoute property as it turns out, _if_ you follow some specific rules, which I have documented and added examples for, and you probably always want to use the plugin in combination with restoring the route (either by using the provided SavedStateRouteObserver or some other mechanism).

Tao just pointed out to me that this was the 4th most requested issue in our survey last fall. I think there are a mix of user desires expressed here (in the over 100 comments!) and we will need to sit down at some point and tease them out into separate items we can take action on.

@eseidelGoogle I disagree with your assessment. IMHO, there are no “mix of user desires expressed” but rather lack of Android platform support which surfaces different programing challenges resulting from the same core issue.

Android applications have two central themes:

1) Process State machine (a.k.a Lifecycle events).
2) UI toolkit.

Flutter does (2) but not (1). It needs to address both.

3 years...

Quick update: I am currently actively working on exploring solutions for this problem.

@goderbauer Just to ensure it's clear that this is not just an Android problem, iOS has the same mechanisms in place, though the impact on iOS apps is probably less. I've created a plugin that allows for saving state through the os mechanisms which might be something to look at too: https://github.com/littlerobots/flutter-native-state

@hvisser how do you recommend saving navigation state? The Navigator from Flutter doesn't have a getRoutes method.

@hvisser Yes, I am looking at this for both platforms (and it should be flexible enough to plug into other platforms as well). I removed the platform-android label to make it clear that this is not an Android specific problem.

@hvisser how do you recommend saving navigation state? The Navigator from Flutter doesn't have a getRoutes method.

@Zhuinden The navigator already has support for it(though I don't think it's well known). I have an example of how to set it up in the native state project, but TL;DR

  • You must be using named routes
  • The routes should be hierarchical, e.g. /screen1/screen2 follows on /screen1 follows on /
  • The routes are pushed to the navigation stack when you set the initialRoutes property and your routes are organised that way
  • I _think_ you also need a GlobalKey for the navigatorKey on the MaterialApp but that might be specific to my case.

So if you set initialRoutes to /screen1/screen2 and you have your routes setup correctly, then restoring navigation will also work.

One more case on old devices with 2 sim cards when you toggle airplane mode on and off the native youtube and flutter apps will restart #49057
Video is attached in my issue

Try to disable flutter debug mode. if debug mode is enabled app always restart.

debugShowCheckedModeBanner: false,

I have been working in flutter since 2018, June/July maybe and came to know this was an issue after 2-3 months. I didn't know it became such a big issue. I was making a music player app and if I pressed back button, flutter used to destroy every state while the music kept playing in the background. To resolve this back then I made this system_shortcuts library .
Add the plugin to your project in pubspec.yaml _[ For those who are beginners :) ]_

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  system_shortcuts:

I have always been using a workaround that works just fine for me but there are a few issues in it, mentioned below in this comment but they don't affect me that much.

Usage -

Whatever widget you are on and on back button press you know that the app will close(i.e user leaves the app) add WidgetsBindingObserver Mixin in the state class like this

class _MusicPlayerState extends State<MusicPlayer>
    with WidgetsBindingObserver{
...
}

After doing that override the didPopRoute method and add the following code in the method

@override
  Future<bool> didPopRoute() async {
    await SystemShortcuts.home();
  return true;
  }

_Lets Understand how this works_ :
App is running >> User presses back button >> Home button is pressed and the back button is not>>The app goes to the background and the state is not destroyed ANY OF IT

Working Sample -

_EDIT_
_This app is written using provider state management but there is no code used to manage any of the state after closing the app and reopening from the recent. Its just the code that we added working here._

ezgif com-video-to-gif

ezgif com-video-to-gif (1)

Issues -

If you have come to your app from another app, whenever you go back from your home widget you'll always go to the home screen and not to that previous app from where you came.
Example :-
User is in WhatsApp (or any other app) >> They get a notification from your app >> They open your app from the notification >> Now they press back button >> They'll go back to home screen of the phone and not WhatsApp(which should be the default scenario)
_This issue has not given me any big problem so far and I think all the normal users and developers will be ok with it as it is in rare cases that you come to your app from another app. I am not neglecting that it can happen quiet a few times but it works for me and its just a workaround till it gets any actual help from flutter core team._

The fact that this problem is almost 3 and half years old and still nothing is so upsetting.

Apparently, this is set to be worked on by May 2020 judging from the currently set milestone… in time for Google I/O?

hvisser above has solved the issue though, now people just need to write the code that makes it work

I am making progress on providing a solution for this. For an early preview of how an app will be able to restore instance state once this is done check out this PR: https://github.com/flutter/samples/pull/433.

Unfortunately, it will take a little more time until its all done.

The design document with details about how to do state restoration in Flutter is available here: http://flutter.dev/go/state-restoration-design

The PR with the general state restoration framework as outlined in the design above has been posted: https://github.com/flutter/flutter/pull/60375. I am still working on adding some more test coverage.

The general state restoration framework (https://github.com/flutter/flutter/pull/60375) has been submitted. I am now working on integrating it in our widgets. Going to start with scrollables, text fields, and the Navigator.

You can implement this yourself by storing the data you want restored inside of a sqlite database. That's a technique that is used when writing servers.

That has the benefit of restoring an app even if the battery dies on the phone or there is a kernel panic or whatever. I recommend making an OO wrapper around your SQLite calls. Dart doesn't have something like SQLite.Net which does it automatically for you unfortunately.

edit: It's also something we did in the early days of mobile development because memory was so limited and sqlite is a great way to swap things between memory and disk.

I am closing this issue since the general state restoration framework (and the Android embedding) has landed. The remaining work is tracked in separate issues:

Well Done!!!!! Thanks flutter!!!

Was this page helpful?
0 / 5 - 0 ratings