How can I save activity state (B) when moving to previous activity (A) and how to restore it's previous state when moving back (to activity B)?
SCENARIO
Content of activity B can be changed by user. After finishing work, performed changes can be persisted (e. g. saved to local DB). Before persisting, user may want to navigate back to previous activity. At this point, "work session" should be somehow saved temporary in memory. After returning back to activity B "work session" should be restored, so user can continue in work.
APPROACHES
Standard methods onCreate(Bundle) and onSaveInstanceState(Bundle) are used to restore/save activity state when device configuration changes (e. g. on rotate). As onSaveInstanceState(Bundle) is not part of activity's life-cycle, this method does not get called on "back-press" when activity is destroyed (by default finish() is called). The same applies for onRestoreInstanceState(Bundle). Obviously these methods alone are not sufficient to restore activity state after lefting an activity.
Transfering bundle state between activities
One way I can think of is to override onBackPressed() in such way it will start an Intent to previous activity A with bundle state Intent#putExtras(Bundle) of current activity B. When moving back, starting an Intent to activity B, this bundle state will be delivered back Intent#putExtras(Bundle) to restore the state of activity B. However this also require to override onCreate(Bundle) and onSaveInstanceState(Bundle) in A so bundle state of B would not be lost on configuration change before navigating back to B.
Not finishing an activity on back-press + non-default launchMode
Another way (more cleaner) is to override onBackPressed() so that it would start Intent to previous activity A without calling finish() of current activity B so activity B will hang in memory in paused state (waiting for resume). To resume activity B, manifest configuration android:launchMode="singleInstance" is required so android will use existing activity (paused B) instead of creating new one (B2) when navigating back to activity B (startIntent(B.class)).
Details: launchMode singleInstance creates singleton activity in new task (task = set of activities with the same group id i. e. affinity, normally app activities have the same affinity i. e. app = single task).
Drawback: transition animations does not work with singleInstance mode. It seems that singleInstance task may not be fully initialized at animation time. For more details: Custom animation doesnt work on SingleInstance Activity.
Saving activity state to SharedPreferences
Saving activity state to SharedPreferences on "back-press", restoring activity state from preferences in onCreate(Bundle) like in following link Save activity state to SharedPreferences.
Drawback: unable to save Bundle state (only primitives: putInt, putString, ...).
Others
Some of the methods listed in Share data between activities can be used. Answers from this link refers to developer.android.com/guide/appendix/faq/framework.html which is unfortunately broken. Here is an alternative source http://wing-linux.sourceforge.net/guide/appendix/faq/framework.html.
About sharing data via Application object:
Don't Store Data in the Application Object,
Using the Android Application class to temporary persist data.
Based on Application class documentation, using static singletons over Application is more preferable.
Base class for maintaining global application state. ...
Note: There is normally no need to subclass Application. In most situations,
static singletons can provide the same functionality in a more modular
way. If your singleton needs a global context (for example to register
broadcast receivers), include Context.getApplicationContext() as a
Context argument when invoking your singleton's getInstance() method.
It seems that putting data in Application object or static singleton is so far the best solution for this problem.
QUESTION
Is there any build-in solution for this (without need of overriding onBackPressed())? For example saving activity on back-stack with it's state. If not, what is the common pattern to save activity state in such situation?
RELATED POSTS (just to link them with this post)
How do I save temporary data on android?
For an alternative way to handle this, you can map your view's states to a data structure and save it as an object notation (such as json) to your local and when your activity is created / re-created you can read your state from local and bind them with your views.
For more information and example, you can check Spotify's presentations and open source project which contains information about how they manage application's ui from apis. (I know it's not exactly what you're trying to do but you may find some tricks.)
If you need to save your data in your activities, you can use Local Db or in-memory cache.
When you back out of an activity, the activity is not just destroyed programmatically, but also conceptually. In other words, the user expects it to be gone. We talk about saving activity state in situations where the activity object is destroyed but the user perceives that it still exists, such as during configuration changes or when it's in the background or backstack.
What you're trying to save is properly thought of as application state, not activity state. As such, SharedPreferences is an appropriate place for it. Whether it's the best solution is a matter of opinion and depends on your use case.
Related
Problem: Sometimes / on some devices the activity calling startActivityForResult (activity A) to launch activity B is being destroyed after calling startActivityForResult & before entering onActivityResult. We get a newly created instance of activity A to return to in onActivityResult - this causes our ViewModel (along with all other member variables) to be lost.
The standard thing to do would then be to restore the ui state using SavedInstanceState. This can't be done in this case due to the size of the object we need to restore - attempting this results in a TransactionTooLargeException. The ViewModel is too large for a Serializable or Parcelable.
Question: Is it possible to force our Activity to be kept intact during this workflow? Or is there another design that would let us avoid this problem? Saving any of the ViewModel's data to disk is not an option.
Context: This is a project where we store a list of images (as byte arrays) taken from the camera one at a time, and some related info about those images in a ViewModel. These are staged in a RecyclerView, where they can be uploaded when the user is done adding images. We add items to this ViewModel by calling startActivityForResult to launch a camera activity and return the resulting image.
We may only be seeing the problem of activity A getting destroyed due to the "Do not keep activities" setting in Developer Options being turned on, and this may not accurately represent how Android would reclaim resources (e.g. the conversation at the bottom of this thread - https://stackoverflow.com/questions/21227623/whats-the-main-advantage-and-disadvantage-of-do-not-keep-activities-in-android#:~:text=Android%20OS%20has%20this%20property,replicate%20the%20same%20scenario%20easily). Still, ideally we want everything to work with this setting on. Right now if activity A is destroyed, we lose our member variables and the ViewModel that we were in the process of building, and don't have a way to recover it.
Storing the ViewModel's data in a fragment (as discussed here: Fragment, save large list of data on onSaveInstanceState (how to prevent TransactionTooLargeException)) won't work since our activity is being destroyed, causing any associated fragments are as well. We actually have a fragment we're using in this way, which loads & holds a list of objects from the server to be selected from and associated with each image - this fragment ends up getting recreated along with the activity when its destroyed and then performs this load again.
No, what you want is not possible. If you launch another Activity using startActivityForResult() and that Activity requires resources, the launching Activity will be killed. There is nothing you can do to prevent this. It is standard Android behaviour and will happen, especially on low-end devices.
If your ViewModel is too large to save as the instance state, you will need to put the data somewhere else: SQLite database or a local file. Then store the name of the file or some key to the database as part of the saved instance state, and when the Activity is relaunched, restore the data from the file or data base.
Note: you shouldn't keep that much data in memory anyway, as you are wasting valuable resources. Only keep the data you really need in memory.
I do know how to save data between config changes (using onSavedInstanceState and checking the Bundle in onCreate, for example, for not being null) and I also do know how to make data persistent (via SharedPreferences), but here I need an in-between-solution.
I have a resource-heavy fragment which gets killed (onDestroyView() and onDestroy() are being called) when I replace it with another resource-heavy fragment. When I return to the former, its state is gone (the Bundle is null), making it persistent would save state across "sessions" which contradicts state-saving in other fragments. Is there a way how have "session-scoped" persistence in Android? By "session-scoped" I mean a period longer than the Android life-cycle and shorter than "forever".
So, there are a bunch of ways of saving the fragment and activity states.
If you have several fragments and an activity, and you are simply replacing the fragment view with those fragments then you may use graph level ViewModel. Check out navigation architecture component.
If you just want to save the state of the fragment, just create ViewModel for fragment view and make its lifecycle as activity lifecycle. So the ViewModel does not get cleared or killed until the host activity for the fragment is destroyed.
You may check out view model here in Android Documentation: ViewModel
Here is the good comparison for managing saved states, which options to consider 1) ViewModel, 2)Saved states or 3) Persistence storage
Depending on the size of data and context you may choose one of the given options. I would fully recommend reading it. Here is the link
Saving UI States
There are 3 activities: A->B->C. Each contains a Button (to open next activity) and a EditText.
For example: if I type some text in C and go back to A(by pressing Back Button), how can I see the same text there?
I know 3 solution:
LocalBroadcastManager
SharedPreferences
Create Singleton class with static field and then get this field in onStart method of A - which cons of this solution?
IMHO, there are always cons in using Singleton design pattern in your applications. Some of them are (from the top of my head):
Coupling between otherwise unrelated objects and flows through Singleton's instance
Emergence of a "global state", which makes debug a lot harder
Inability to mock static fields and methods through "conventional" mocking
The fact that a reference to Singleton can be easily obtained in any part of the application leads to a total mess (people stop thinking about dependency graph)
Singletons tend to breed: you introduce one, then another one, then you find yourself with 10 singletons which hold app's state in a "global cloud state"
Note that what you're trying to do is against Android guidelines - if the user taps on "back" button, then he should find the previous Activity or Fragment in the exact same state it had the last time the user saw it, without any additions (unless you explicitly don't want to save it in the back-stack).
If you still want to do it, then I could suggest several options:
Use SharedPreferences and store the value there. Get the value in each Activity and diplay it in onResume()
Use startActivityForResult() call in order to start new Activities and pass the value back in the result. Note that by default press on "back" cancels the action, therefore you'll have to override onBackPressed() method.
Override onBackPressed() method in Activity in such a way that it starts another Activity (instead of just popping the back-stack) and pass the value in the Intent that you use. You might want to use FLAG_ACTIVITY_CLEAR_TOP in this case.
Use some event bus that supports "sticky" events. When user inputs the text you post a sticky event to event bus. In onResume() of Activity you check whether event of this type exists and if it is - you update UI.
Once again - the fact that you CAN do what you want doesn't mean it SHOULD be done.
Simply set into onResume() method of your class A, a call to the Singleton class instance you want to save (or istance of Application class which is the same)
LocalBroadcastManager is not a reliable option. It assumes the bottom activity to still be alive, which might not be the case. While you use B, A might be collected to free resources. Also, you should unregister LocalBroadcastManager receivers onResume(). So, no.
Singletons with static fields are generally to be avoided. It’s not worrying for a single string of text, but singleton fields are easily forgotten and can lead to memory leaks. Better to avoid this pattern if possible, and in your case it is.
Possible options.
If the field is something that must persist, e.g. user editing his username, use SharedPreferences or another storing solution (saving to a server, saving to cache, saving to device SQLite database).
If the field is temporary and is the result of activity B, you can start activity B with startActivityForResult() and then send the result back to activity A through an Intent.
For your problem the simpliest solution - store your value in Application class. Any activity can access it and read/write values.
Cons is that if you accidentally store static reference to activity, it will cause memory leak.
You may try using EventBus for horizontal communication: Activity-> Service, Service -> Fragment, etc.
It has static instance by default plus you can subscribe/unsubscribe to it in onPause and onResume methods.
Another advantage is a STICKY EVENTS - you can post event from Service and it will wait until something handle it - Activity will receive this event when it is ready - after onResume().
When starting a new application for a client, I am asking myself again the same question about who should be responsible for loading data: activities or fragments. I have taken both options for various apps and I was wondering which pattern is best according to you in terms of:
limiting the code complexity.
handling edge cases (like screen rotation, screen going power save, loss of connectivity, etc.)
Option 1 - Activity loads data & fragment only displays it
This allows to have fragments that are just fed a bunch of objects to display. They know nothing about loading data and how we load that.
On the other side, the activity loads data using whichever method is required (for instance initially the latest 50 entries and on a search, loads the search result). It then passes it to the fragment which displays it. Method to load the data could be anything (from service, from DB, ... fragments only know about POJOs)
It's kind of a MVC architecture where the activity is the controller and fragments are the view.
Option 2 - Activity arranges fragments & fragments are responsible to fetch the data
In this pattern, fragments are autonomous pieces of application. They know how to load the data they are displaying and how to show it to the user.
Activities are simply a way to arrange fragments on screen and to coordinate transitions between application activities.
In theory you can do whatever you want, if it works.
Actually, the fragments and activities display data and deal with their own life cycles.
Since fragments belongs to activity so you have to use both in conjunction to better handle all the data but mostly it depends on your needs.
If you keep in mind the idea that the Fragment should provide the UI and the Activity should provide the processing then you have a good division of concerns and code which should allow the Fragment or the Activity to be reused.
If you know about the MVC - Model View Controller - design pattern then you can think of the Fragment as the View and the Activity as the Model.
Things get much more interesting when you build an application with multiple Fragments.
Some key points as a decide factor -
The idea of a Fragment is that it is a wrapped up chunk of UI that
can be used by any Activity that needs it. On this basis you have to
ask yourself if the event that has to be handled is the same for
every Activity or unique to each Activity. If it is the same then the
event handler is better written within the Fragment.
The Fragment doesn't have a UI of its own - it is displayed by an
Activity that the Fragment is associated with. The events are
generated by objects in the View hierarchy, which is owned by the
Activity. If you try to use Android Studio to add an event handler,
for example, it will add it to the Activity and not to the Fragment.
You can define the EventListener that you want to handle the event
in the Fragment and then hook it up to the View object in the
Activity in which you want to generate the event.
A Fragment is a class that implements the onCreateView method to
supply a View hierarchy that can be displayed by an Activity.
To use a Fragment in an Activity you have to add it using a
FragmentManager and a FragmentTransaction. You can add the Fragment
using the add method but nothing happens until you call the commit
method.
After the method that used the commit, usually the Activity's
onCreate, terminates the CreateView event runs the Fragment's
onCreateView and the Fragments View hierarchy is added to the
Activity's content.
You have to write code to save and restore any additional state the
Fragment may have.
If a task is common to all instances of the Fragment then its code
should live in the Fragment.
In particular the code to handle events can be defined within the
Fragment.
The Activity should be used to host code that processes the data
provided by the UI.
Attaching Activity event handlers to the Fragment's UI or is
difficult to do correctly.
From scenarios make decision what your app will be. Is it service,
activity, widget , even a content provider or a complex system,
including some different components. Test your decision against
scenarios.
All of these have to work after the Fragment has been destroyed and
recreated.
(1) Initialization of the Fragment, (2) Saving and restoring the Fragment's
state and (3) Implementing something like an event mechanism so the Fragment
can get the Activity's attention
The hardest part is implementing something like an event mechanism.
In the case of the complex system, distribute functionalities and
data entities among application components. Make a list of components
and what they are (activities or smth else).
Make the list of UI components with description what they do (not HOW
yet) These will be widgets and activities or fragments or layouts
later.
Often you will want one Fragment to communicate with another, for example
to change the content based on a user event. All Fragment-to-Fragment
communication is done through the associated Activity. Two Fragments
should never communicate directly.
When your app is perfectly modular, fragments don't know about each
other. You can add a fragment, remove a fragment, replace a fragment,
and they should all work fine, because they are all independent, and
the activity has full control over the configuration.
You can't do anything with a Fragment unless you start a transaction.
Within the transaction you can set up what you want to happen,
usually add the Fragment to the current layout, but nothing happens
until you use the commit method.
Efficient handling of data with Screen Orientation -
When screen orientation changes, Android restarts the running Activity (onDestroy() is called, followed by onCreate()).
To properly handle a restart, it is important that your activity restores its previous state through the normal Activity lifecycle, in which Android calls onSaveInstanceState() before it destroys your activity so that you can save data about the application state. You can then restore the state during onCreate() or onRestoreInstanceState().
However, you might encounter a situation in which restarting your application and restoring significant amounts of data can be costly and create a poor user experience. In such a situation, you have two other options:
1) Retain an object during a configuration change
Allow your activity to restart when a configuration changes, but carry a stateful Object to the new instance of your activity.
2) Handle the configuration change yourself
Prevent the system from restarting your activity during certain configuration changes, but receive a callback when the configurations do change, so that you can manually update your activity as necessary.
What I would do is manage all data flow (bluetooth, database storage, etc)
in the Activity and use Fragments only for UI display or handling user input.
This way is easier to handle configuration changes/ screen rotations.
Also, if data flow things are heavy to be on UI thread, consider using a Service with a background thread.
If it is a "one shot" thing, you can use an IntentService,
otherwise you can implement a Bind Service and request a bind from anywhere you have Context.
For more read - fragment-and-activity-working-together.
Ideally neither Activity nor Fragment with UI should contain any "model" logic - these classes should be lightweight and responsible only for UI logic. But when you decide to make a separate model object you have a dilemma to choose where to initialise and store this object and how to deal with configuration changes. And here comes some handy trick:
You can create a model Fragment without UI, make it retain instance to deal with configuration changes (it's AFAIK the simplest way to save data across config. changes without troubles) and retrieve it anywhere you need via findFragmentById(). You make all expensive operations inside it once (using background thread, of course), store your data and you're done.
For more info, see Adding a fragment without a UI section.
UPD: There's now a better way to deal with configuration changes: ViewModel from Google's Architecture Components. Here's a good example.
I prefer and always implemented Option-2 over Option-1.
Reasons for not selecting Option-1:
We should have listeners for events triggered in Fragments and pass it back to activity to load data, process it and push it back to fragment, which makes work more complex.
An Activty can load any number of Fragments, Typically you end up questioning these questions to yourself in a scenario where your app is highly scalable and is already huge. Writing all the events in an activity and passing it over to fragment will be an complex altogether.
As #Ved Prakash mentioned, Handling screen orientation becomes complex if orientation is handled by Activty.
I have an example:
your application have 2 features A and B. the 2 features are independent each other. and each feature has a lot of screen.
you should create 2 activities A and B because when Activity A is used, Activity B should be released to reduce memory of app. And the same when B is used, A should be released. The memory of Context A and B are independent, if you want to send data from Activity A to B you must use intent or use global variable in Application Context. intent is managed by OS, not by application. When A send intent to B, if A is destroy is no problem with intent send to B. Activity is App module, it is can call by other applications (fragment is impossible).
for example: feature A has a lot of screen (ex: Fragment1, Fragment2). they use the same data and depend on each other. each screen should be a fragment. and when process with data you can get reference to data by calling function getActivity() and then get reference to variable of Activity context (or Activity memory) to write or read it. Fragment1 and Fragment2 are belong to Activity A Context.it means that Fragment 1 and Fragment 2 can transfer data with each other via variable of Activity context, it is easy to do . noticed that Intent is managed by OS,it is so expensive when send data via Intent.
For restoring the state of the activity after it is recreated (for instance after the screen-orientation change) I implemented onSaveInstanceState() and onRestoreInstanceState(). It is simple to save/restore simple information like int, double etc. But what about saving/restoring objects like Timer?
You cannot store "live" objects (like db connections) in the Activity arguments or saved instance data. Those mechanisms are designed so that the application can be completely stopped, so it only works with things that can be "serialized" and later restored.
What you can do is use fragments. If you add a fragment without UI (check here, look for
“Adding a fragment without a UI”) and call on it setRetainInstance(true) the fragment will get reattached to the activity, surviving any configuration change.
Hope it helps. (Remember you can use fragments with old Android versions by using the support package)