android - what happen in fragment onDetach - android

I am using nested fragments in the bottom navigation. at the first time, all fragments were loaded and everything is fine but when I changed the fragment, null exception fired and I saw that getParentFragment() return null since onDetach get called I need to know that in child fragment what happens in that func? child fragment gets detach from its parent and I have to set it again? how to avoid this detaching?
public void switchFrag() {
Fragment displayedFragment;
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
if (isListFragmentDisplayed) {
displayedFragment = mapFragment;
transaction.replace(R.id.fragment_contaner, displayedFragment, "map").commit();
} else {
displayedFragment = listFragment;
transaction.replace(R.id.fragment_contaner, displayedFragment, "list").commit();
}
isListFragmentDisplayed = !isListFragmentDisplayed;
}

The detach method removes the fragment from the UI, but its state is maintained by the Fragment Manager. This means you can reuse this fragment by calling the attach method, with a modified ViewHierarchy

Nested fragments are supported as of Android 4.2.
you can use getChildFragmentManager() ...
Detach:
Detach the given fragment from the UI. This is the same state as when
it is put on the back stack: the fragment is removed from the UI,
however its state is still being actively managed by the fragment
manager. When going into this state its view hierarchy is destroyed.

Related

How to detect if a fragment is presented or hidden?

I am maintaining a backstack of fragments and pop the stack when back button is pressed. I need to reload data every time a fragment is made visible and do some cleanup when it gets hidden. For this I need to detect when a fragment is shown and hidden. This is a very common question but surprisingly none of accepted answers work for me. I
I am adding fragments to backstack using code like this:
public void pushFragment(Fragment f) {
getFragmentManager().beginTransaction()
.add(R.id.content_frame, f, null)
.addToBackStack(null)
.commit();
}
I am popping fragments off using this code:
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 1) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
I am trying to detect from a fragment class when it becomes visible or hidden (either because it is being popped off the stack or another fragment was pushed on top). So far I have tried these callbacks:
onViewCreated/onDestroyView: Only called when the fragment is added to stack and popped off the stack. Not called when the fragment gets hidden or visible because of other fragments on the stack.
onHiddenChanged: Never called. Many have said on SO that this works. But not working for me for some reason.
setUserVisibleHint: Never called
onStart/onPause etc: They don't really apply here because they simply reflect the lifecycle of the host activity.
Is there a Fragment callback that will let me detect when a fragment is being shown or hidden? I will rather not use a backstack listener because I want every fragment class to have its own show/hide logic.
Edit:
If I use replace() to add the fragment (instead of add()) then the view for the previously shown fragment gets destroyed. As a result if that fragment ever to appear on top of the stack again its view is recreated. In this situation onViewCreated/onDestroyView or onStart/onStop will be called every time a fragment is shown or hidden. I suppose I could use that approach. The down side is that the views are created and destroyed frequently. I might as well be using activities instead of fragments for master-detail navigation in that case.
Edit again
If you need a callback you can override onHiddenChanged like this:
public void onHiddenChanged(boolean hidden) {
if(hidden){// Do you stuff here}
}
As PPartisan mentioned you claimed that onHiddenChanged is never called.
The reason for that is because onHiddenChanged doesn't get called the first time an fragment is shown.
Called when the hidden state (as returned by isHidden()) of the fragment has changed. Fragments start out not hidden; this will be called whenever the fragment changes state from that.
To fix this: add this to your code:
FragmentTransaction mFragmentTransaction = getFragmentManager().beginTransaction();
if (f!= null) {
mFragmentTransaction .hide(f);
}
mFragmentTransaction.add(R.id.content_frame, f, null)
mFragmentTransaction.addToBackStack(null)
mFragmentTransaction.commit();
More on the on this SO thread
From the Android documentation:
void onStart () Called when the Fragment is visible to the user. This
is generally tied to Activity.onStart of the containing Activity's
lifecycle.
void onStop () Called when the Fragment is no longer started. This is
generally tied to Activity.onStop of the containing Activity's
lifecycle.
The correct way is using Fragment Lifecycle's methods.
This is get called then the fragment is changing visibility
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {}

Why am I getting Fragment not Attached to Activity Error?

I have a thread which triggers a callback method in my Activity which has a fragment. The Activity then calls a method within the Fragment to refresh the UI with new info. In that method, getString() is called. When getstring() is called, I get a 'Fragment not Attached to Activity error'. This UI refreshing error is guarded runOnUIThread. The fragment is added like so:
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction()
.add(fragmentContainerID, fragment, tag)
.commit();
fm.executePendingTransactions();
The activity uses two different layout files for horizontal and vertical orientation. Their are two fragment containers in both layouts. They have the same names in both horizontal and vertical layouts, but they do not match each other. The fragments are added using the above code the first time the Activity is created. On orientation changes, the fragments are automatically added into the layouts because the fragment containers have the same ids in both vertical and horizontal orientation.
When the savedInstanceState is not null, I get a reference to the fragments by using
fm.findFragmentByTag
Can anyone please tell me what I am doing wrong ? The fragments are visibly added to the Activity perfectly fine, and several minutes go by before this thread callback occurs, so I don't see how the Fragment is not "attached".
EDIT: Sigh, after some debugging Ive found that adding fragments in this way is perfectly fine. Code elsewhere in the Activity ( I registered for a callback twice) caused my fragment not attached error. If anyone reading this is getting this error, keep in mind that this issue can be triggered from elsewhere. Also, you have to setRetainInstance to true in the fragment to avoid it being recreated. Tags alone don't do that.
Fragments are added one-per-container so if you are using .add(fragmentContainerID, fragment, tag) ensure firstly that fragmentContainerID is different for each of the fragments added.
However, these sorts of issue are most often caused by a misunderstanding in how orientation changes are handled. When you change orientation and let the oncreate handle the adding: you get a new fragment of the same type what this means is that if your background task has a reference to your fragment - it has a reference to the old (now detached) fragment. What you need to do is to make sure the same fragment gets added to the tag (not a new instance). To do that just don't (re)create a fragment instance when we are only restoring state
public class MyActivity ... {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
MyFragment frag = new MyFragment();
frag.setArguments( getIntent().getExtras() ); //if it takes params
fm.beginTransaction().add(fragmentContainerID, frag, tag).commit();
}
//after this point a call to MyFragment frag = (MyFragment) fm.findFragmentByTag(tag); should always return the correct fragment
}
It's probably also a wise idea for your task to look for the fragment right when it does its callback to update the UI (and NOT to hold on to a reference to it). Depending on how you've set up your task it just means changing from FragmentManager fm = getSupportFragmentManager(); then the task is created, to calling it as part of the onPostExecute
When change orientation, fragment automatically added to FragmentManager and forget own tag!!
Use this code to find your fragment:
FragmentManager fragmentManager= getSupportFragmentManager();
for (Fragment fragment : fragmentManager.getFragments())
{
if(fragment instanceof MyFragment)
myFragment = (MyFragment)fragment;
}
After you find the fragments on orientation change, try detaching them and then reattaching them before any operation. Or if that does not work remove them and add them. then continue..... I have even had to sometimes put a time delay of a few millisec after detach and before attach to ensure all OK.

Android Fragment onCreateView triggered when it shouldn't

I have one activity and a couple of fragments. My activity has a drawer list that I use to switch fragments and roughly looks like this:
public class MainActivity extends AppCompatActivity
{
private PreferencesFragment preferencesFragment;
private HomeFragment homeFragment;
private void selectMenuItem(position)
{
Fragment fragment = null;
switch (position) {
case 0:
if (preferencesFragment == null) {
preferencesFragment = new PreferencesFragment();
}
fragment = preferencesFragment;
break;
case 1:
if (homeFragment == null) {
homeFragment = new HomeFragment();
}
fragment = homeFragment;
break;
}
getFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
}
}
So whenever I click on a drawer list item, selectMenuItem is called with the correct position. This loads an existing fragment or creates a new one when needed. The problem is that, even when a fragment already exists, the onCreateView method is triggered in the Fragments. I want to maintain the state of the view and only execute the code in onCreateView once. The docs say this about onCreateView:
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
So why is it being called whenever selectMenuItem is executed? I verified that it's actually not executing the new Fragment() block and loading the properties value. How can I maintain the state of my Fragments when I'm using just 1 activity?
savedInstanceState is also null in the onCreateViewMethod in the Fragments. onSavedInstanceState is never triggered in the Fragments.
Yes, that's how the fragment lifecycle works. replace() removes any existing fragments, and removing a fragment destroys its view. Reusing the same Fragment instance in a later fragment transaction will trigger the lifecycle again from the beginning.
As Ivan mentions in his comments, onSaveInstanceState() only gets triggered when the hosting activity's onSaveInstanceState() is triggered, and merely replacing a fragment within an activity leaves the activity intact. You can save the state yourself, see e.g. android fragment- How to save states of views in a fragment when another fragment is pushed on top of it
To allow your fragments to get garbage collected, consider not keeping references to them.
If you replace fragment with another fragment -- views get destroyed, that's the correct behavior. If you really need to avoid view recreation, you can use FragmentTransaction.hide(), but imho your current approach is right, you don't know whether user will ever want to see PreferencesFragment so no point in bloating memory with unnecessary hidden views.

What to do with Fragments when they are dismissed (not visible)?

I am rewriting a Bluetooth app with 3 Activities to use just 1 Activity and 3 Fragments:
So I have now 4 files:
MainActivity.java (contains bluetooth and shared preferences code)
MainFragment.java (contains ellipsis menu to show SettingsFragment)
SettingsFragment.java (contains "scan" button to show ScanningFragment)
ScanningFragment.java (displays nearby bluetooth devices in a list)
It almost works, but as an Android programming newbie I don't understand - what to do with Fragments when I show some other Fragment?
Should I just drop the Fragments (and remove from FragmentManager?) to be garbage collected?
Or should I add these 3 private variables to MainActivity.java and reuse them (when the user navigates forwards and backwards)?
private MainFragment mMainFragment;
private SettingsFragment mSettingsFragment;
private ScanningFragment mScanningFragment;
Or does FragmentManager somehow manage all 3 Fragment for me - regardless if they are visible or not?
Here is my current code (it is simple, I just call replace() all the time)-
public class MainActivity extends Activity implements
MainListener,
SettingsListener,
ScanningListener,
BleWrapperUiCallbacks {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Fragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "settings")
.commit();
break;
}
return super.onOptionsItemSelected(item);
}
// implementing SettingsFragment.SettingsListener interface
public void scanClicked() {
// TODO how to stop indicator when returned?
setProgressBarIndeterminateVisibility(true);
String address = // get from shared preferences
Fragment fragment = ScanningFragment.newInstance(address);
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "scan")
.commit();
}
Should I just drop the Fragments (and remove from FragmentManager?) to
be garbage collected?
No need to do anything else. FragmentManager is the guy in charge of Fragments' lifecycle. Once you call replace(), FragmentManager takes care for the rest. If needed it will keep fragment in memory, or release it.
Or should I add these 3 private variables to MainActivity.java and
reuse them (when the user navigates forwards and backwards)?
No, don't do it because of the said above.
Or does FragmentManager somehow manage all 3 Fragment for me -
regardless if they are visible or not?
Yes, it does. For instance, if you have invisible retained fragment, it's enough to create it once, and FragmentManager will take care of it and will keep it even when activity gets re-created during configuration change.
If you create fragments dynamically (as far as I can see, this is your case) then I suggest to add very first fragment dynamically too. You can do it like this.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_main); // empty FrameLayout
if (savedInstanceState == null) { // <- important
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
}
This will ensure you don't duplicate MainFragment on configuration change, because when savedInstanceState is not null, then FragmentManager keeps instance of your fragment already.
Since you are calling .replace() on the fragment manager it's essentially the same thing as calling .remove(). According to the docs:
This is essentially the same as calling remove(Fragment) for all currently
added fragments that were added with the same containerViewId and
then add(int, Fragment, String) with the same arguments given here.
So you don't need to worry about any further management since it will be taken care of for you (and be removed to free up resources). This basically means that when one is shown the other is removed. If you were to call .add() then the fragments would still be alive in the background using up resources but you don't have to worry about that since using .replace() only allows one to live at a time.
If I understand your question correctly, you need not call any method to destroy the fragments after using them. Android OS will take of them. According to the documentation, When you replace the fragment with another, the onStop() method of the fragment will be executed, and documentaed as,
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.
So the fragment will be killed by the OS when the activity is killed. Till the activity is live, fragment objects will reside in the memory.
EDT:
So, if you want to use the fragment again in future, as document suggests,
Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated(). For more information about saving state, see the Activities document.
Fragment are hard coded in xml can not replaced....
but when you call replace(..) then what happen ??
Ok just consider you have 3 fragment A,B,C . In primary stage A initialize in MainActivity... now you are going to call B from A using replace(....). that means A will go onPause(),onStop() state of lifecycle and B will be initialized and paced in MainActivity...same as for C then B is onPause() ,onStop() state. If you want to reuse A or B then you need to call again A or B from C using replace (..). and then A or B will reinitialize and C goes to onPause(),onStop(). But there is another way you can do this
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
hide(A);
show(B);
ft.commit();
if you use above code block then A still be in the running state of its lifecycle but not visible (just detached from UI ).. so it would be better if you need those fragment again use hide()or show() method because it is less expensive operation then reinitialize the fragment.

Fragments not destroyed when recreate() activity

I have a support library fragment that sends a network call, and recreate() the parent activity when it receives a specific network response. I can see that the activity does get recreated as I can see the onCreate() and onDestroy() are called.
But after the activity is recreated, the fragment is still there and it got stuck in a loop which keep recreating and making new fragments.
Here's part of the onCreate() of the activity:
if (someLogic()) {
fragmentA = new FragmentA();
FragmentUtil.addFragment(getSupportFragmentManager(), fragmentA);
} else {
fragmentB = new FragmentB();
FragmentUtil.addFragment(getSupportFragmentManager(), fragmentB);
}
FragmentA is the one that does the network call, and FragmentB is the fragment that should be displayed after the recreate(). When I check the list of fragments with getSupportFragmentManager().getFragments() I see 1 instances of FragmentA, and 16 instances of FragmentB.
My question is why does this happen, and how do I fix it?
Calling recreate() essentially causes the Activity to go through a configuration change. A configuration change causes both the Activity and its Fragments to be destroyed and recreated. So your Fragments should in fact be destroyed when this occurs (you can see for yourself by adding a Log message in your fragment's onDestroy method).
The fact that configuration changes cause fragments to be destroyed and created however does not mean that the FragmentManager will simply forget the fragments ever existed. The FragmentManager will still hold a reference to the newly created fragment after the configuration change occurs.
One thing you can do to prevent multiple fragments from being created is by doing something like this in your activity's onCreate():
if (savedInstanceState == null) {
// The Activity is being created for the first time, so create and
// add new fragments.
} else {
// Otherwise, the activity is coming back after being destroyed.
// The FragmentManager will restore the old Fragments so we don't
// need to create any new ones here.
}
I had same situation as #spy wrote, recreating the Activity after Day/Night mode is changed. Activity had four Fragments which they get restored after Activity.recreate() is called, because FragmentManager saves them as #Alex Lockwood stated. But restoring and not adding Fragments after recreation does not help me because of some architectural reasons of project, i wanted them to disappear.
Solution is to delete Fragments from FragmentManager just before Activity.recreate()is called, in my case it is in Activity.onStart():
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragmentA = fragmentManager.findFragmentByTag("fragmentATag");
if(fragmentA != null)
fragmentTransaction.remove(fragment);
// Find and delete other Fragments
fragmentTransaction.commit(); // or commitAllowingStateLoss()
recreate();
This way you will not get Fragments restored when Activity.onCreate() is called.

Categories

Resources