When switching between multiple fragments is it necessary to start a new FragmentTransaction and remove each fragment when finished or, for example, if references to the fragments are stored as instance variables (to provide access to fragment methods from the host activity) is setting the instance variables to null when the fragment is replaced sufficient?
I am concerned if each fragment is not removed that it somehow lives on at least until a new fragment with the same id is added to replace the old one.
Once the fragment is replaced with another fragment it will call the onDestroy method of the replaced fragment.
is setting the instance variables to null when the fragment is replaced sufficient?
There is nothing already in there when you replace the fragment and when you call it, let say a getter method it will return null because onDestroy already clean out all the data that was inside of that fragment.
Related
I am adding and removing Views to/from my Activity dynamically. Each of these Views is assigned an id and acts as a container for a particular Fragment. I add a Fragment to each one of these Views with conditional logic as follows:
if (supportFragmentManager.findFragmentById(R.id.someView) == null) {
supportFragmentManager.beginTransaction()
.add(R.id.someView, SomeFragment())
.commit()
}
This conditional logic ensures that a given View only has a Fragment added to it once during the lifetime of the Activity.
This logic works fine except when the Activity is recreated (due to a configuration change for example). When the Activity is recreated, the Views are not automatically recreated but the Fragments appear to survive the recreation. (I see that the Fragments have survived the recreation because the supportFragmentManager.findFragmentById(id:) calls return a non-null Fragment.)
I find that if I re-add Views to my Activity in the Activity.onCreate(savedInstanceState:) method, then the retained Fragments re-attach fine to the Views and everything is fine. However, if I delay adding the Views to a later point in the Activity lifecycle, then the Fragments do not re-attach to the Views (and the Views show up as blank).
Ultimately, this leads to confusing logic in my Activity.onCreate(savedInstanceState:) method when savedInstanceState is non-null to work around this. Either I have to re-add Views as they were at the point when the Activity was destroyed (I would prefer to do this elsewhere in the Activity) or I have to call FragmentTransaction.remove(fragment:) to remove each Fragment which survived the recreation.
Is there a way to add a Fragment to an Activity such that the Fragment does not survive Activity recreation? I see in the deprecation notice for the Fragment.setRetainInstance(retain:) method that the guidance is: "Instead of retaining the Fragment itself, use a non-retained Fragment and keep retained state in a ViewModel attached to that Fragment." However, this guidance does not give any instruction on how to define a non-retained Fragment.
There are a couple of dimensions to this answer.
Firstly, I could not find any documentation or any methods in the FragmentManager or FragmentTransaction classes which offer a means of creating a non-retained Fragment. The documentation in the deprecated Fragment.setRetainInstance(retain:) method says to use a "non-retained Fragment" but I could not find anywhere that explains what this means.
Secondly, the workaround for this problem is to remove the retained Fragment in the containing Activity's onCreate(savedInstanceState:) method so that the problematic Fragment can be recreated and attached to its containing view in a later lifecycle method, as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.some_activity)
supportFragmentManager.findFragmentById(R.id.someView)?.let {
supportFragmentManager.beginTransaction().remove(it).commit()
}
}
In my application I create a fragment with the keyword new and set it by FragmentTransaction.
Upon rotation a stumbled upon a NullPointerException in the method onActivityCreated() indicating a missing injection, that I do after the call to new. I suspected the fragment was not created by my code und proved this by logging the hashCode(). It looks like a fragment is created automatically by the system upon rotation.
Where does it come from?
Is it created by the fragment manager?
How am I supposed to use it correctly?
How can I access it, to set the missing value?
For now I ignore it by testing for the null value, in which case onActivityCreated() does nothing. Instead use the fragment I create with new. However, this does not feel very satisfying, to throw away an object, that was already created.
Where does it come from? Is it created by the fragment manager?
On Activity recreation, Android will restore the fragments which already exist in activity's fragments manager
How am I supposed to use it correctly?
public void onCreate(Bundle savedInstanceState){
if(savedInstanceState == null){
//activity is created for first time
//commit the fragment
}else{
//Activity is recreated(by means of rotation or something else)
//Dont commit the fragment, fragmet will be restored by the system
}
}
How can I access it, to set the missing value?
Normally, you have to handle this inside the fragment using onSaveInstanceState method. You can get the fragment instance by using, getSupportFragmentManager.findFragmentById(R.id.container) or getSupportFragmentManager.findFragmentByTag(tagName)
I have Fragment which I replace by another one. I put transaction to backstack so I can move back later. If I press back button saveInstanceState Bundle of restored Fragment is null in it's methods cause saveInstanceState method of Fragment is actually called when parent Activity instance destroyed. So how I must restore Fragment state after returning it from backstack?
This problem was mostly related to ListViews. I have found a solution in more correct managing of adapter data. I had local mAdapter variable in my Fragment which was created, populated with data and set to ListView in onResume() method of my Fragment. I have found solution in moving this code into onActivityCreated() method.
First of all I'm sorry if this explanation seems unclear, I'm new to Android.
I have a ViewPager in main activity showing fragments added dynamically by user. Fragments are created initially on activity start up and are added to ViewPager via Adapter i.e. adapter simply returns proper fragment and as I understand correctly fragment's content is created at this time when ViewPager 'retrieves' a fragment first time.
The problem is when main activity gets restored after orientation changing all fragments are resurrected as well and when Adapter tries to return newly created by user Fragment method createView() is no longer called and it fails with NullPointerException. It seems ViewPager retains fragments attached to it initially and doesn't call createView() for newly added ones for the same position.
I have a feeling I'm missing vital point on the Fragment lifecycle. I wouldn't like to change the design. My main question is what the correct way is to return a Fragment added to ViewPage after activity is restored? Is there any way to locate recently attached fragments?
If the fragment already exists, it will be re-used. However the state of the fragment will not. You should take a look at http://developer.android.com/training/basics/activity-lifecycle/recreating.html for more information.
In particular you should look at overriding onSavedInstanceState and onRestoreInstanceState as a method to repopulate the field that is generating that nullpointerexception
To determine if an instance of a fragment has already been created you can use 'findFragmentByTag' like so:
String fragmentTag = MyFramgment.getClass().getName();
MyFragment frag = getFragmentManager().findFragmentByTag(fragmentTag);
if(frag == null)
frag = new MyFragment();
Whatever ends up being referenced in 'frag', show this to the user.
Using Tabs means using fragments, and for some reason fragments have new steps in their life cycle, like onAttach(Activity).
My fragment fills up some maps from the resources, and it is done on onAttach() instead of the fragment constructor; because in the constructor getResources() throws an exception due to the lack of Activity yet.
The fragment is created on MainActivity like this:
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
switch( tab.getPosition() ) {
case 0:
if( fragmentTab0 == null ) {
fragmentTab0 = new MyFragment();
setTabText(0, ((MyFragment)fragmentTab0).getMyName());
}
fragmentTransaction.add(R.id.fragmentContent, fragmentTab0, "TAB0");
break;
Here lies my problem, in the call to the fragment method getMyName() which uses the maps I mentioned before to get a string. The call to getMyName() is executed before the fragment's onAttach() and the maps are not ready yet.
I am sure I can find a convoluted way to get the name (actually I tried already to pass the activity to the fragment's constructor and built the maps there, and it works, but it goes against the fragment philosophy).
I would have thought that the activity should be visible during Fragment constructor, since the fragments are created from the activity they're going to be eventually attached, so there is no point in delaying the activity attachment.
I also would have thought that the call to new MyFragment() should return after onAttach() is done. But it returns right after the constructor is done.
Therefore I feel not comfortable with the situation and I wonder if I am using fragments the wrong way, if so, the question is, how am I supposed to do it right to be able to call getMyName() there.
Note: From the fragment life cycle diagram it is clear that onAttach() and onDetach() are indistiguishable from onCreate() and onDestroy() respectively, so I question if they are really necessary.
onAttach() is not invoked until the fragment transaction is committed. Until then, a fragment has no reference to the creating Activity unless you pass it such a reference. Passing that reference is probably the cleanest way to implement what you're trying to do.
Generally in Android the title is dictated by the containing activity, not the fragment. For example, a PreferenceActivity's xml headers file lists titles and their associated fragments; those titles do not appear in the preference xml files used by the PreferenceFragment.