Android Fragments Retaining Data - android

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.

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.

Why is Bundle not suitable for large data, and ViewModel is?

I read this, and it says:
For simple data, the activity can use the onSaveInstanceState() method
and restore its data from the bundle in onCreate(), but this approach
is only suitable for small amounts of data that can be serialized then
deserialized, not for potentially large amounts of data like a list of
users or bitmaps.
My question is why? How does the ViewModel differs from Bundle with regards to making the data persistent between instances?
Data stored in Bundle is serializable and could survive process die - you can restore it after application is launched again. On the other side, ViewModel only survive during configuration change (for example, rotation of the screen) and didn't save it's data if component die.
Bundle is class used to pass data between components of android like activities, fragments etc.
Mainly for activity, when activity is under any configuration changes, bundle helps to save some small amount of user data to be restored on new configuration applied. Due to limited memory provided for any App, it's stated that,
this approach is only suitable for small amounts of data that can be serialized then deserialized, not for potentially large amounts of
data like a list of users or bitmaps.
Also, it is not anything related to activity lifecycle.
From ViewModel guide, it's stated that
The ViewModel class is designed to store and manage UI-related
data in a lifecycle conscious way. The ViewModel class allows
data to survive configuration changes such as screen rotations.
So, any data which you need to survive any configuration changes can be part of ViewModel class, it can be any large amount of data.
Let me know if still confused, hope it helps !
Edit :
Thin line difference is
Bundle is something which is provided by activity once it's under configuration change. So, the OS parcels the underlying Bundle of the intent. Then, the OS creates the new activity (yes, it's new object of same activity), un-parcels the data, and passes the intent to the new activity.
ViewModel objects are something that are provided by ViewModelFactory which is out of context for activity & it doesn't rely on activity instance.

What's the best way to preserve a model across android configuration changes?

In my application, I have a fragment which renders a model object fetched from the server. The model class implements Parcelable so when the device is rotated, I save the model in onSaveInstanceState() and then I load the model in onCreate(). If the savedInstanceState bundle does not contain the model in onCreate(), I make the network request again.
My question is: Is it better to preserve the state of the model by having it implement Parcelable and saving it in onSaveInstanceState() OR is it better to simply set the Fragment's setRetainInstanceState(true).
In my opinion setInstanceState should be used only in special cases, while onSaveInstanceState() should be used as standard approach to keep fragment's state. This is common way for keeping state of all Android's components.
Keeping fragment in memory can lead to situations like strange views behavior and memory leaks.
The API Guide recommends using a retained headless data fragment.

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.

How to save data members of Fragment during configuration change?

I have a fragment that creates an AsyncTask to download and parse a RSS feed, then displays it in a list. The problem is, downloaded feed is stored in a RSSFeedobject, and that becomes null when the fragment is recreated after a screen rotation. That means every time the user rotates the screen, the app must re-download the feed, wasting time and bandwidth. I could have it load a cached copy of the xml but that still takes time and is bad UX.
Up until now I've been using setRetainInstance(true) in the fragment, and it seems to work. However, I've recently read that it is not recommended to use setRetainInstance(true) in a fragment with a UI, something about leaking context. I've also seen other people say that it's ok and even recommended, as long as you reassign a value to views once the activity is recreated. I'm not sure how accurate either answer is, some help here would be appreciated.
Assuming I don't use setRetainInstance(true), and the fragment is recreated on configuration change, I'd like a way to preserve just that object. If it was a string or int I know to use onSaveInstanceState and a bundle, but the thing is this feed isn't serializable, nor is it recommended to serialize and deserialize a potentially large object. So the second question is, what can I do to preserve the feed so I don't have to reload it again?

Categories

Resources