What is the difference between Fragment.onSaveInstanceState(…) and retaining the fragment--android - android

I want to save the fragments state, and after referring all over, I came across two ways of doing so,
Retaining the state of fragment using setRetainInstance(true) in onCreate() method of fragment
and overriding Fragment's onSaveInstanceState(Bundle outState) method.
I tried using both and both worked for me.
I was wandering, are there any specific use cases, when to use any of them?

The major difference between overriding Fragment.onSaveInstanceState(…) and
retaining the fragment is how long the preserved data lasts. If it needs to last long enough to survive configuration changes, then retaining the fragment is much less work.
This is especially true when preserving an object, you do not have to worry about whether the object implements Serializable.
However, if you need the data to last longer, retaining the fragment is no help. If an activity is destroyed to reclaim memory after the user has been away for a while, then any retained fragments are destroyed just like they are not retained.
For an app with a small list of say quiz question,having users start over may be an acceptable choice.
But if the quiz had say 100 questions, Users would rightly be irritated at returning to the app and having to start again at the first question. You need the state of the fragment to survive for the lifetime of the activity record. To make this happen, you would use onSaveInstanceState(…).
Hope this clarifies.

The difference is how long the preserved data is retained. setRetainInstance(true) is basically useful for retaining data while a configuration change occurs (like rotation) for a very brief instant.
If the data needs to be retained while the user navigates to another app (or home screen, etc) then you should override setRetainInstance (because Android may reclaim your Activity's memory.)

Related

Can we persist a large state object through activity destruction?

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.

Is there any need to use onSaveInstanceState and onRestoreInstanceState when using Android Architecture Components LiveData & ViewModel?

Android Architecture Components provide the LiveData and ViewModel classes which are more lifecycle-friendly and designed for a leaner Activity/Fragment. These classes handle storing data across configuration changes, but I'm confused about their use compared to the Activity framework APIs. Are onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) still necessary or useful for preserving activity state?
onSaveInstanceState & onRestoreInstanceState is still useful.
ViewModel holds data only when process is alive.
But, onSaveInstanceState & onRestoreInstanceState can hold data even if process is killed.
ViewModel is easy to use and useful for preserving large data when screen orientation changes.
onSaveInstanceState & onRestoreInstanceState can preserve data when process is in background.(in background, app process can be killed by system at anytime.)
Assume a scenario :
user is in activity A , then navigates to activity B
but because of low memory Android OS destroys activity A , therefor the ViewModel connected to it also destroys. (You can emulate it by checking Don't keep activities in Developer options)
now user navigates back to activity A, Android OS try's to create new Acivity and ViewModel objects. therefor you loosed data in ViewModel.
But still values in savedInstanceState are there.
As well as the other answers which talk about the ViewModel's persistence beyond simply configuration changes, I think there are a couple more use cases:
Performance reasons
Sometimes you don't want to store all of the latest values of view attributes in the ViewModel for performance reasons. You may have greater need to save them when the view is being re-created. For example, user's scroll position on a view within your activity/fragment. You probably don't want to save the scroll position every time the user scrolls. But you might want to save that onSaveInstanceState so you can restore that when the view is recreated (onRestoreInstanceState).
Initialization to perform after restore
Some views may require initialization especially because of the restore, due to the complex design of those views not being able to save everything. For example, I had a WebView and if the user was in the middle of loading a page during a configuration change, I want the WebView to try to load the new page (rather than the old one). After restoring the state, the observers of LiveData will get the latest values but this doesn't help much with something like this (I only want the view to load a page from the ViewModel at the point of restore, not at other times). So we just do that initialization via the restore state.
Final word
With all this stuff I would advocate keeping your onSaveInstanceState and onRestoreInstanceState as simple as possible. Ideally just call a method on the ViewModel and that's it. Then we can extract all of the logic from the view into the ViewModel, and the view is just left with boilerplate code.

Android Fragments on Backstack taking up too much memory

PROBLEM:
I have an Android application that allows a user to browse to a user's profile ViewProfileFragment. Inside ViewProfileFragment a user can click on an image that will take him to StoryViewFragment where various users' photos show up. It is possible to click on a user profile photo that will take them to another instance of ViewProfileFragment with the new user's profile. If a user repeatedly clicks on user's profiles, clicks an image that takes them to the gallery then clicks on another profile the Fragments stack up in memory quickly causing the dreaded OutOfMemoryError. Here is a diagram flow of what I am describing:
UserA clicks on Bob's profile. Inside Bob's profile UserA clicks on ImageA taking him to a gallery of photos of various users (including Bob's). UserA clicks on profile of Sue then on one of her images - process repeats, etc, etc.
UserA -> ViewProfileFragment
StoryViewFragment -> ViewProfileFragment
StoryViewFragment -> ViewProfileFragment
So as you can see from a typical flow there are lots of instances of ViewProfileFragment and StoryViewFragment piling up in the backstack.
RELEVANT CODE
I am loading these in as fragments with the following logic:
//from MainActivity
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.activity_main_content_fragment, fragment, title);
ft.addToBackStack(title);
WHAT I'VE TRIED
1) I am specifically using FragmentTransaction replace so that the onPause method will be triggered when the replace takes place. Inside onPause I am trying to free up as many resources as I can (such as clearing out data in ListView adapters, "nulling" out variables, etc) so that when the fragment is not the active fragment and pushed onto the backstack there will be more memory freed up. But my efforts to free up resources is only a partial success. According to MAT I still have a lot of memory that is consumed by GalleryFragment and ViewProfileFragment.
2) I've also removed the call to addToBackStack() but obviously that offers a poor user experience because they can't traverse back (the app just closes when the user hits the back button).
3) I have used MAT to find all of the objects that I take up a lot of space and I have dealt with those in various ways inside the onPause (and onResume) methods to free up resources but they are still considerable in size.
4) I also wrote a for loop in both fragments' onPause that sets all of my ImageViews to null using the following logic:
for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) {
View h = shell.getChildAt(i);
ImageView v = (ImageView) h.findViewById(R.id.galleryImage);
if (v != null) {
v.setImageBitmap(null);
}
}
myListViewAdapter.clear()
QUESTIONS
1) Am I overlooking a way to allow a Fragment to remain on the backstack but also free up its resources so that the cycle of .replace(fragment) doesn't eat up all of my memory?
2) What are the "best practices" when it is expected that a lot of Fragments could be loaded onto the backstack? How does a developer correctly deal with this scenario? (Or is the logic in my application inherently flawed and I'm just doing it wrong?)
Any help in brainstorming a solution to this would be greatly appreciated.
It's hard to see the whole picture (even tho you have shown us a lot of information), without concrete access to your source code, which I'm sure it would be impractical if not impossible.
That being said, there are a few things to keep in mind when working with Fragments. First a piece of disclaimer.
When Fragments were introduced, they sounded like the best idea of all times. Being able to display more than one activity at the same time, kinda. That was the selling point.
So the whole world slowly started using Fragments. It was the new kid on the block. Everybody was using Fragments. If you were not using Fragments, chances were that "you were doing it wrong".
A few years and apps later, the trend is (thankfully) reverting back to more activity, less fragment. This is enforced by the new APIs (The ability to transition between activities without the user really noticing, as seen in the Transition APIs and such).
So, in summary: I hate fragments. I believe it's one of the worst Android implementations of all time, that only gained popularity because of the lack of Transition Framework (as it exists today) between activities. The lifecycle of a Fragment is, if anything, a ball of random callbacks that are never guaranteed to be called when you expect them.
(Ok, I am exaggerating a little bit, but ask any Android seasoned developer if he had trouble with Fragments at some point and the answer will be a resounding yes).
With all that being said, Fragments work. And so your solution should work.
So let's start looking at who/where can be keeping these hard references.
note: I'm just gonna toss ideas out here of how I would debug this, but I will not likely provide a direct solution. Use it as a reference.
WHAT IS GOING ON?:
You're adding fragments to the Backstack.
The backstack stores a hard reference to the Fragment, not weak or soft. (source)
Now who stores a backstack? FragmentManager and… as you guessed, it uses a hard live reference as well (source).
And finally, each activity contains a hard reference to the FragmentManager.
In short: until your activity dies, all the references to its fragments will exist in memory. Regardless of add/remove operations that happened at Fragment Manager level / backstack.
WHAT CAN YOU DO?
A couple of things come to my mind.
Try using a simple image loader/cache lib like Picasso, if anything to make sure that images are not being leaked. You can later remove it if you want to use your own implementation. For all its flaws, Picasso is really simple to use and has come to a state where it deals with memory "the right way".
After you have removed the "I may be leaking bitmaps" problem out of the picture (no pun intended!), then it's time to revisit your Fragment lifecycle. When you put a fragment in the backstack, it's not destroyed, but… you have a chance to clear resources: Fragment#onDestroyView() is called. And here is where you want to make sure that the fragment nullifies any resources.
You do not mention if your fragments are using setRetainInstance(true), be careful with that, because these do not get destroyed/recreated when the Activity is destroyed/recreated (e.g.: rotation) and all the views may be leaked if not properly handled.
Finally, but this is harder to diagnose, maybe you'd like to revisit your architecture. You're launching the same fragment (viewprofile) multiple times, you may want to consider instead, reusing the same instance and load the "new user" in it. Backstack could be handled by keeping track of a list of users in the order they are loaded, so you could intercept onBackPressed and move "down" the stack, but always loading the new/old data as the user navigates. The same goes for your StoryViewFragment.
All in all, these are all suggestions that came from my experience, but it's really hard to help you unless we can see more in detail.
Hopefully it proves to be a starting point.
Best of luck.
It turns out that fragments share the same lifecycle as their parent activity. According to the Fragment documentation:
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle. For
example, when the activity is paused, so are all fragments in it, and
when the activity is destroyed, so are all fragments. However, while
an activity is running (it is in the resumed lifecycle state), you can
manipulate each fragment independently.
So the step that you took to clean up some resources in onPause() of the fragment wouldn't trigger unless the parent activity pauses. If you have multiple fragments that are being loaded by a parent activity then most likely you are using some kind of mechanism for switching which one is active.
You might be able to solve your issue by not relying on the onPause but by overriding setUserVisibleHint on the fragment. This gives you a good place to determine where to do your setup of resources or clean up of resources when the fragment comes in and out of view (for example when you have a PagerAdapter that switches from FragmentA to FragmentB).
public class MyFragment extends Fragment {
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//you are visible to user now - so set whatever you need
initResources();
}
else {
//you are no longer visible to the user so cleanup whatever you need
cleanupResources();
}
}
}
As was already mentioned you are stacking items up on a backstack so it's expected that there will be at least a little bit of a memory footprint but you can minimize the footprint by cleaning up resources when the fragment is out of view with the above technique.
The other suggestion is to get really good at understanding the output of the memory analyzer tool (MAT) and memory analysis in general. Here is a good starting point. It is really easy to leak memory in Android so it's a necessity in my opinion to get familiar with the concept and how memory can get away from you. It's possible that your issues are due to you not releasing resources when the fragment goes out of view as well as a memory leak of some kind so if you go the route of using setUserVisibleHint to trigger cleanup of your resources and you still see a high-volume of memory being used then a memory leak could be the culprit so make sure to rule them both out.

What are good cases not to use setReatinInstance(true) in a fragment?

Since orientation changes happen fairly quickly one would think that keeping a Fragment in memory during that time would be more efficient than recreating it again.
Since it's kept for a short time only, there seem to be little impact on memory.
What then shall be good reasons NOT to use setRetainInstance(true) for each and every Fragment?
What then shall be good reasons NOT to use setRetainInstance(true) for each and every Fragment?
Google's primary concern is that you'll screw up and have data members in the fragment that refer to the old activity that you do not clean up in post-configuration lifecycle method calls (e.g., onCreateView()). For example, you might hold onto a widget in a data member, where you do not immediately null out or repopulate that data member on a configuration change. If your fragment has a reference back to the old activity, the old activity (and everything it holds onto) cannot be garbage-collected until your fragment gets destroyed. This is one of the reasons why Google does not recommend retaining any fragment with a UI.
Firstly maybe check this link: http://android-er.blogspot.com/2013/05/how-setretaininstancetrue-affect.html
Basically you shouldn't always retain the instance of the fragment because fragments are attached to activities. Hence when an activity is re-created (i.e. on configuration change), the fragment needs to be re-associated to the new activity (which leads to extra coding and some extra problems). If you just unnecessarily setRetainInstance(true), you are giving yourself more error checking and coding to do for no reason. By setting the setRetainInstance(true), you will need to deal with a different fragment lifecycle as well because certain methods in the lifecyle are now skipped (i.e. the onCreate() is no longer called after configuration changes). As far as I understand, setRetainInstance(true), won't make it more efficient because you could use onSaveInstance to save any data that you would want to use in the recreation of the same kind of fragment.
I hope this helps.

Android Fragments Retaining Data

How does one best retain data within a Fragment as its activity goes through an onCreate/Destroy cycle like from Rotation?
In our setup we have potentially large lists loaded from our servers into the fragments custom list adapter and we want to smooth out the UX by not making them reload on rotation. The problem we had with setting the fragment retainInstance=true; is that our adapter has a reference to the context of the original activity and would therefore leak memory. Could we just store the data in the fragment and re-create the adapter; amd if so is that really proper practice?
The next idea is to store the data into a session singleton object and retrieve after rotation which presents a few problems with stale data but we can easily overcome.
The other alternative I see, that seems like it is the *best solution, is to save the data into a bundle and restore into the new fragment after rotation; However, we have quite a few objects that would need to be stored throughout the app and some of our objects are complex, contain lists, multiple types, and would be a pain to make parcelable. Is there a better solution or do we have to bite the bullet and make them Parcelable?
Just prevent the Activity from recreating itself on rotation (etc). Add
android:configChanges="keyboardHidden|orientation|screenSize"
to your Activity definition in your AndroidManifest.xml. Then there's no need for saving anything on rotation.
EDIT:
If you don't like that solution then you've got no choice but to use the onSaveInstanceState mechanism. If you've got complex data, just make your classes Serializable and add them to the Bundle that way.
Setting the
android:configChanges
attribute in Android manifest is the hackiest and most widely abused workaround for disable the default destroy-and-recreate behavior.
See more about that at
Handling Configuration Changes with Fragments :
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
According to http://developer.android.com/guide/topics/resources/runtime-changes.html,you completely can save the data in the fragment as long as it isn't associated with the activity, views, etc. Bundles are really not meant for heavy amounts of data and serializing is slow, so a fragment is ideal for large amounts of data.
It might not be possible for you to completely restore your activity state with the Bundle that the system saves for you with the onSaveInstanceState() callback—it is not designed to carry large objects (such as bitmaps) and the data within it must be serialized then deserialized, which can consume a lot of memory and make the configuration change slow. In such a situation, you can alleviate the burden of reinitializing your activity by retaining a Fragment when your activity is restarted due to a configuration change. This fragment can contain references to stateful objects that you want to retain.

Categories

Resources