In my code, I use two methods to pass data to a new fragment. Either I pass data through Bundle or sometimes write setters to pass data.
Both works fine, no issues faced yet.
But now, I am optimising my code keeping in mind savedInstances, orientation changes or any other possible way where data can be lost.
So, the exact doubt in my mind is whether the data sent via bundle remains intact by default on orientation change / fragment restored from background. Or we have to use savedInstance in the case of bundles as well. As per my knowledge, data set through setters get lost.
Whenever the OS needs to re-layout your view, it will call onCreate and onCreateView with a saved instance state. If you are using a constructor and passing variables, you will lose whatever you set. If you are using a bundle and are using it to directly change some of your variables, you will likely overwrite them with the original values in your bundle. To get around this, just check if the bundle is null before performing the mutation.
TLDR: Passed in bundle will remain intact through orientation changes and instance restores. You can add extra data to the saved instance state bundle in onSaveInstanceState.
As you said, OS can recreates your fragment (across a configuration change or when OS need to reclaims memory), data will be lost. And you consider between using saveInstanceState and passing Bundle as fragment's argument.
Using bundle as fragment's argument is less complicated, easier to maintain. However, argument must be set before fragment is attached to activity, that means you can not change argument later. So, if your passing data is fix, argument bundle is the best choice
If your passing data can be changed while running, setter + saveInstanceState is the only way to go
Related
In Android Fragment it has onSaveInstanceState() which allows to save some data for restore the state when os recreates the fragment.
onSaveInstanceState(outState: Bundle) {
outState.putString("dataStr", dataStr)
}
What about the data are already in fragment's argument? Seems after the fragment is recreated the previous arguments are also restored.
// set the argument
Bundle bundle = new Bundle();
bundle.putString("dataStr", dataStr);
AFragment aFragment = new AFragment();
aFragment.setArguments(bundle);
//get the argument inside the AFragment
Bundle bundle=getArguments();
Does it mean if anything is in fragment's argument then they don't need to be saved through onSaveInstanceState()?
The question: since the argument can be updated inside the fragment, is it a alternative for saving the data in the argument instead of saving through the onSaveInstanceState()?
Or any difference with using these two?
onSaveInstanceState() / onRestoreInstanceState() is only explicitly called by Android when the Activity/Fragment needs to be recreated (especially on configuration changes), imagine that a user started to type something and then changed the orientation of his screen, if you didn't handle the saving state he will loose his inputs.
Fragment Args on the other hand are used to instantiate fragments, the args will be available even if the Fragment is recreated BUT it will be the initial state.
So to summarize if you want to create a new fragment with some args (like a User Name...) you need to use args.
But if you want to save the current state (User Inputs...) in case of fragment recreation you need to handle it through the SaveInstance method.
For further understanding of how Save/Restore Works I recommend this article
savedInstance : saveInstanceState when you are frequently moving back and forth between activities and remember this thing when you close app mean it get removes from memory, information will also lost.
Bundle :
Bundle uses a Map for holding the extras you’re putting into. so in this way there’s no difference between a Map and Bundle. but when you’re going to send data to another Activity(or any other IPC components such as Service, BroadcastReceiver,…) the Map will be marshalled into byte[] via a Parcel, and will send to destination. in destination the data will go into the same flow in reverse and the byte[] will be unmarshalled into Map and you can have extras by providing the same keys.
So Bundle is like a Map which can also be marshalled/unmarshalled to/from byte[]. one important note is that you cannot use this byte[] to persist your bundle, as it’s designed ONLY to transfer to components, and will not work if underlying data structure changes.
I have a fragment that is always visible. I don't understand why I should use bundles to pass data to it from activity.
Most of the questions here recommend this method of passing data:
Bundle bundle=new Bundle();
bundle.putString("name", "From Activity");
Fragmentclass fragobj=new Fragmentclass();
fragobj.setArguments(bundle);
I prefer creating Fragment object in OnCreate function of activity and then use this object to display fragment(FragmentTransaction.add). As I have refence to this fragment I can create create function showName() in it and call it from activity like that:
myFragment.showName("name");
Is there anything wrong with this approach?
The Android documentation states:
Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
That's why it's better to use a bundle and set the parameters of the Fragment this way, it's easier for the system to restore its values when the fragment is re-instantiated.
Now, I wouldn't use myFragment.showName("name"); because you don't know if the lifecycle of the fragment has already finished (attached to the activity and inflated the views), so instead, I would call the showName("name") in the onActivityCreated or onViewCreated callbacks.
Applications should generally not implement a constructor. The first place application code can run where the fragment is ready to be used is in onAttach(Activity), the point where the fragment is actually associated with its activity. Some applications may also want to implement onInflate(Activity, AttributeSet, Bundle) to retrieve attributes from a layout resource, though should take care here because this happens for the fragment is attached to its activity.
There's nothing wrong with this approach for setting one off data values, you just need to be careful to make sure that the view that you want to set your name on actually exists at the point that the showName method is called.
Part of the reason that using Bundles to pass information is popular is that they can hold all types of data using keys and also they can easily be used to pass view states around during device rotation. Ultimately it's a matter of preference and exactly what your use case is.
When app is in background, the Fragment can recreated (eg: by change the theme (light/dark), language, ...).
So if you dont pass data use Bundle to Fragment, your Fragment will not have this data when it recreated
More to the point, in onCreate/onCreateView I am already calling FragmentManager.findFragmentByTag() to lookup any existing instance of my fragment, and it seems to find it.
So what is the point of putFragment/getFragment? Does it save something extra or cause additional lifecycle stuff to happen? Is it just an alternative to findFragmentByTag() that does more or less the same thing? Because it seems to me that the fragment is being automatically saved for me without needing to use FragmentManager.putFragment().
So what is the point of putFragment/getFragment?
According to the current implementation, what putFragment(Bundle bundle, String key, Fragment fragment) do is put the index of a fragment into the bundle with the parameter key. And then getFragment(Bundle bundle, String key) get the fragment at the same index which can be retrieved from the bundle with the same key. A Fragment has its index in the FragmentManager only after it is added to it, so putFragment() can be called on a Fragment only after it is added.
Does it save something extra or cause additional lifecycle stuff to
happen?
It save the index of a Fragment only, no more things else, nor do it cause any additional lifecycle stuff.
Is it just an alternative to findFragmentByTag() that does more or
less the same thing?
Yes, I thik so.
According to the current implementation, what putFragment/getFragment does can be achieved with findFragmentByTag() too. But the function of putFragment/getFragment are quite limited because you can't use them without the bundle parameter, means you must call putFragment() in onSaveInstanceState().
Seems like putFragment/getFragment its just a safe way of storing fragments and its states inside fragment manager without displaying it.
For example you have 2 fragments that stored in your activity fields. You displaying one of them and than replace it another after that you change orientation of screen. Fields in your activity are reinited but fragment that currently displayed saved its state and other don't. But if you store fragments inside fragment manager you will have two fragments with actual states.
I'm trying to do this without using a bundle. The reason I am trying to avoid that is because the data is pulled from a database online and I don't want the fragments to not load while it's waiting to get the data. From what I can tell I can only do the bundle arguments if the data is ready to send before any of the fragments are created.
This would cause a problem because the data isn't pulled from the database until the ViewPager is created. I can successfully pass the variables, but like I said the data isn't there instantaneously. This ends up sending a zero value through the bundle variable.
Am I incorrect in assuming you can't still pass bundles after the fragment creation? Aside from Bundles, what would be the best way to send a variable from the ViewPager to the Fragment? Would storing the data in the cache be a good option?
You can setup a method within your fragment to be called from your activity once you have retrieved said data from database. Then within your fragment method you can load it to where ever you need and however you need.
Only thing you have to be concerned about is the static context of the method.
You could cast your Activity to Fragment.getActivity then invoke a getter for your data.
Alternatively, you could use a Loader and implement LoaderManager.LoaderCallbacks in your Fragment to return the data from your database, but only initialize it once the data has been set.
I suppose you could also show some sort of loading Fragment, then replace it with the other after the data is set. You could use a Bundle in that case.
There are lots of ways to solve this. And yeah, the Fragments arguments can't be passed after creation, unless you call Fragment.setArguments before it's attached to the Activity.
Source
Fragment.instantiate
Fragment.setArguments
I'm having some trouble understanding how to make a simple DialogFragment to edit a (complex) object, say a Person, with first and last name, and a list of e-mail addresses each consisting of an enum (Work, Home, etc) and the address.
First of all, how do I properly pass the Person object to a DialogFragment? My current solution has a setPerson(Person person) method, that's called after my DialogFragment is created, but before dialog.show(). This works ok, until a configuration change happens (user rotates the screen). The DialogFragment gets recreated and the reference to my Person object is null. I know I can save the instance using onSaveInstanceState, but the object is complex and expensive, and persisting a large object this way seems wasteful.
I've also tried disabling configuration change in the activity that uses my dialog, and that fixes the problem, but I want the dialog to be reuseable and requiring all the activities that use it to disable configuration changes seems wrong.
Third option would be to save the reference to Person in a static variable, but again, I want the dialog to be reuseable and able to support multiple instances.
How do other people handle their expensive and complex objects in reuseable dialogs?
Well, there are several solutions, none of which are fantastic or failsafe if you are completely unable to serialize the object you're editing.
I don't recommend ever using android:configChanges="orientation" unless it's absolutely, 100% unavoidable. There are other configuration changes, and your app will still break with the others if you resort to using that solution.
But a simple solution that will work in the vast majority of cases is to call setRetainInstance(true) on the DialogFragment. This will prevent your Fragment from being destroyed and re-created on a configuration change. There is an edge-case where this might not work, though. There are other reasons besides configuration changes where the OS will attempt to put an activity or app 'on ice', for example to save memory. In this case, your object will be lost.
The cleanest way to pass a complicated Object to a fragment is to make the Object implement Parcelable, add the object to a Bundle, and pass the bundle to the Fragment with fragment.setArguments(bundle). You can unpack the Object in onActivityCreated() of the fragment by retrieving the bundle through a call to getArguments().
To persist the argument on configuration changes, simply save the "working" parcelable Object to the bundle provided by onSaveInstanceState(Bundle state) method of the fragment, and unpack the argument later in onActivityCreated() if savedInstanceState !=null.
If there is a noticeable performance hit from implementing Parcelable, or you have a "live" object of some kind, one option is to create a non-UI fragment to hold the data object. Without getting into details, you can setRetainInstance(true) on the non-UI fragment and coordinate the connection with the UI fragment through interfaces in the Activity.