Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instance state not saved when app is killed by OS #6827

Closed
LouisCAD opened this issue Nov 12, 2016 · 140 comments
Closed

Instance state not saved when app is killed by OS #6827

LouisCAD opened this issue Nov 12, 2016 · 140 comments
Assignees
Labels
a: annoyance Repeatedly frustrating issues with non-experimental functionality c: new feature Nothing broken; request for a new capability customer: crowd Affects or could affect many people, though not necessarily a specific customer. f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels. P0 Critical issues such as a build break or regression

Comments

@LouisCAD
Copy link

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.

@eseidelGoogle eseidelGoogle added engine flutter/engine repository. See also e: labels. framework flutter/packages/flutter repository. See also f: labels. platform-android Android applications specifically labels Nov 12, 2016
@eseidelGoogle
Copy link
Contributor

#3427 is also likely related.

@LouisCAD
Copy link
Author

@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?

@Hixie
Copy link
Contributor

Hixie commented Nov 12, 2016

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

@LouisCAD
Copy link
Author

@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…?

@Hixie
Copy link
Contributor

Hixie commented Nov 14, 2016

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.

@LouisCAD
Copy link
Author

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.

@Hixie Hixie added this to the 4: Make shippers happy milestone Feb 27, 2017
@Takhion
Copy link

Takhion commented Sep 24, 2017

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?

@LouisCAD
Copy link
Author

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

@Takhion
Copy link

Takhion commented Sep 27, 2017

@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?

@Hixie
Copy link
Contributor

Hixie commented Sep 29, 2017

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.

@sethladd
Copy link
Contributor

sethladd commented Oct 7, 2017

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?

@LouisCAD
Copy link
Author

LouisCAD commented Oct 7, 2017

@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.

@sethladd
Copy link
Contributor

sethladd commented Oct 7, 2017

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

@Takhion
Copy link

Takhion commented Oct 7, 2017

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

@mit-mit
Copy link
Member

mit-mit commented Oct 9, 2017

@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 ?

@DanielNovak
Copy link

DanielNovak commented Oct 10, 2017

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

@DanielNovak
Copy link

DanielNovak commented Oct 10, 2017

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 (?).

@zoechi
Copy link
Contributor

zoechi commented Oct 10, 2017

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

@sethladd
Copy link
Contributor

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?

@Takhion
Copy link

Takhion commented Oct 11, 2017

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.

@raju-bitter
Copy link
Contributor

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?

@DanielNovak
Copy link

@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.

@zoechi
Copy link
Contributor

zoechi commented Oct 18, 2017

@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.

@goderbauer
Copy link
Member

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: flutter/samples#433.

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

@goderbauer
Copy link
Member

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

@goderbauer
Copy link
Member

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

@goderbauer
Copy link
Member

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

@gaaclarke
Copy link
Member

gaaclarke commented Jul 31, 2020

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.

@goderbauer
Copy link
Member

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:

@luffyjie
Copy link

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

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 11, 2021
@flutter-triage-bot flutter-triage-bot bot added P0 Critical issues such as a build break or regression and removed P2 labels Jun 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: annoyance Repeatedly frustrating issues with non-experimental functionality c: new feature Nothing broken; request for a new capability customer: crowd Affects or could affect many people, though not necessarily a specific customer. f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels. P0 Critical issues such as a build break or regression
Development

No branches or pull requests