In the example on using fragments in the Android docs, when the application is in 'dualview' mode, the details fragment is recreated whenever the application needs to show details for a different title. FragmentTransaction.replace() is used to swap out each old details fragment instance with a new one.
Is this recommended practice? Isn't it wasteful to create a new UI instance when the real intent (no pun intended) is to update what the UI shows, not the UI itself. It seems to me the only reason to create new instances is if one intends to add them to the backstack so the user can retrace steps. Otherwise, is it safe/advisable to update a fragment directly?
In the case of the example, it would mean a method along the lines of DetailsFragment.setShownIndex(). This would be called, passing in the new title index, instead of recreating DetailsFragment.
Suppose we have a version of the example where one activity manages both fragments, but only shows one at a time, swapping each fragment out as needed. Would it be ok for the activity to create an instance of each fragment, retain references to each, and then simply add or remove these two instances from itself as needed?
One possibly sticky consequence of this would be that, when the titles fragment is in resumed state (i.e. in the 'foreground'), selecting a title will result in a call to DetailsFragment.setShownIndex() at a time when the details fragment is in stopped state.
Good idea? Bad idea?
Thanks in advance.
Like you said, the main reason to create new Fragment instances is for ease of using the back stack. It is also perfectly safe to reuse an existing Fragment (looking it up using either FragmentManager.findFragmentById() or FragmentManager.findFragmentByTag()). Sometimes you'll need to make good use of the Fragment methods like isVisible(), isRemoving() etc. so you don't illegally reference UI components when the DetailsFragment is stopped.
Anyway in your proposed single-pane Activity with 2 fragments, your setShownIndex method could set a private field in DetailsFragment which is loaded in onCreateView or onActivityCreated.
e.g.,
DetailsFragment df = getFragmentManager().findFragmentByTag("details");
if (df != null) {
df.setShownIndex(getSelectedIndex());
} else {
df = DetailsFragment.newInstance(getSelectedIndex());
}
fragmentTransaction.replace(R.id.frame, df, "details").commit();
In both cases, whether df is newly created or reused, onCreateView and onActivityCreated will be called when the DetailsFragment gets added to the container.
But if you want a back stack, I highly recommend just creating new instances, otherwise you're just implementing your own back stack for the contents of the DetailsFragment.
I have tried the following code and it works for me :
private void replaceFragment(Class fragmentClass, String FRAGMENT_NAME, android.support.v4.app.FragmentManager fragmentManager) {
Fragment fragment = null;
String backStateName = fragmentClass.getName(); // nome della classe del Fragment
Log.d("Fragment: ", "Creazione Fragment: "+backStateName);
Boolean fragmentExit = isFragmentInBackstack(fragmentManager, backStateName);
if (fragmentExit) { //Il Fragment รจ presente nello stacback
// Fragment exists, go back to that fragment
//// you can also use POP_BACK_STACK_INCLUSIVE flag, depending on flow
fragmentManager.popBackStackImmediate(fragmentClass.getName(), 0);
} else {
// se non esiste lo aggiungiamo
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// Inizializzo la transazione del Fragment
android.support.v4.app.FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setCustomAnimations(
R.anim.fragment_slide_left_enter,
R.anim.fragment_slide_left_exit,
R.anim.fragment_slide_right_enter,
R.anim.fragment_slide_right_exit);
ft.replace(R.id.frameLayout_contentMain, fragment, FRAGMENT_NAME);
ft.addToBackStack(fragmentClass.getName());
ft.commit();
// Recupero il numero di Fragment presenti
Integer nFragment = fragmentManager.getBackStackEntryCount();
Log.d("Fragment: ", "Numero di Fragment: "+nFragment);
}
}
To determine if the Fragment is already in StackBack execute this function :
public static boolean isFragmentInBackstack(final android.support.v4.app.FragmentManager fragmentManager, final String fragmentTagName) {
for (int entry = 0; entry < fragmentManager.getBackStackEntryCount(); entry++) {
if (fragmentTagName.equals(fragmentManager.getBackStackEntryAt(entry).getName())) {
return true;
}
}
return false;
}
I Hope I could help you
Related
I am using NavigationDrawer in my application and each menu item in drawer is a fragment.Whenever user chooses a menu item I replace the current fragment in the main container with the requested one but it recreates the fragment every-time, so i updated my code to reuse the existing fragments instead of creating them again and again as content of fragments remain same. My updated code to show fragment is :
public void showTabFragment() {
TabFragment Tf = (TabFragment) mFragmentManager.findFragmentByTag(Constants.TAB_FRAGMENT);
mFragmentTransaction = mFragmentManager.beginTransaction();
if (Tf != null) {
mFragmentTransaction.replace(R.id.containerView, Tf, Constants.TAB_FRAGMENT);
} else {
mFragmentTransaction.replace(R.id.containerView, new TabFragment(), Constants.TAB_FRAGMENT);
}
mFragmentTransaction.commit();
}
In above code I am trying to get fragments by Tag but it always returns null and executes the else case(new fragment).Could someone please guide me what am I doing wrong in my code?
I guess the code you've shown is for one of your menu fragment? If that's the case, what is probably happening is every time you open a menu item, the container is replaced with the new fragment(say, Fragment B) with its new tag(say, TAG 'B'). So, when you try to open the previous fragment(say, Fragment A) using it's tag(TAG 'A'), it won't be there, because that's what you replaced.
One possible solution is to hold references to the fragment as they are created, in, say a hashmap, and reuse them instead.
private HashMap<String, Fragment> menuFragments = new HashMap<>();
public void showMenu(String fragmentID)
{
MenuFragment fragment = menuFragments.get(fragmentID);
if(fragment == null)
{
fragment = new MenuFragment(); //Create the respective menu fragment based on the ID.
menuFragments.put(fragmentID, fragment);
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.containerView, fragment, fragmentID);
transaction.commit();
}
i have a internal discussion about what way is better to share info between fragments contents inside a controller activity. In a first classical way, you can set arguments when you are going to replace fragments as follows:
//Just now i'm inside Fragment 1 and i'll navigate to Fragment 2
Fragment newFragment = getFragmentManager().findFragmentByTag(Fragment2.TAG);
Bundle b = new Bundle();
b.putBoolean("test1", true);
// Create new fragment and transaction
if(newFragment==null)
newFragment = Fragment2.newInstance(b);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)//.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim)
.replace(R.id.fragment_place, newFragment, Fragment2.class.getName())
.addToBackStack(newFragment.getClass().getName())
.commit();
The newInstace method does as i meant above, so, with setArguments:
public static Fragment2 newInstance(Bundle arguments){
Fragment2 f = new Fragment2();
if(arguments != null){
f.setArguments(arguments);
}
return f;
}
But Fragment1 and Fragment2 they are both inside a ControllerActivity, so i can also think about a second way to share information obtained in Fragment1 towards Fragment2, through declaring attributes in the ControllerActivity, so i could do (declaring previously an object in the activity) as follows inside any fragment:
EDIT
public class ControllerActivity extends FragmentActivity{
int value = 5;
...
And then, inside my fragment:
((SplashActivity)getActivity()).value = 10; //i can assign or recover value when i desire
My question is what inconveniences would have doing as the second way.
Writing code using 2nd way is fast. But the problem is you have to cast the general Activity to the more specific SplashActivity in which the value variable exists. If you want to use the Fragment with another Activity, or you want a Fragment to be a general purpose UI component you have to use interface for passing the data.
As mentioned in comments, bellow links provide more details about interface/callback method:
android docs
video from slidenerd
Hope this answers your question.
In my android application, a fragment will be added to the activity by a certain action (for example, the action bar menu).
This is the code I add the fragment:
case R.id.action_add_box:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.place, BoxEditFragment.newInstance(null, null));
ft.addToBackStack(null);
ft.commit();
break;
Now once the user hit the action menu with id action_add_box two times, then he have to hit the back two times to close the fragment which is not expected.
Is it possible to avoid this?
For example, once user hit the action menu, do nothing if the fragment have been already visible to the user?
And one more question, there are some EditTexts in the fragment, once user complete, I will submit the data and close the fragment, however user may need to open the fragment again, and I want to keep the value of the EditText as last entered by user. Now I save the values when the fragment are detached and reset the value when created using the savedInstanceState.
Also create a new instance of the fragment for each action command is a waste of memory, I wonder if I can use only one fragment instance, then I may not need to save/reset the values manually?
you can use singleton parttern to keep one instance of fragment for eg:
public class MyFragment extends Fragment{
public static MyFragment oneInstance = null ;
private MyFragment(){
super();
}
public static MyFragment getInstance(){
if (oneInstance == null ){
synchronized (MyFragment.class){
if ( oneInstance == null ){
oneInstance = new MyFragment();
}
}
return oneInstance ;
}
}
the above code is also thread safe
MyFragment frag= (MyFragment )getFragmentManager().findFragmentById(R.id.your_fragment_layout);
if(frag == null){
// fragment is not visible
}else{
// fragment is visible
}
Recently I'm reading the source code of FragmentActivity(sorry that I can't find the source in github, I'm using a native source jar file). The FragmentManager contains the following two members:
ArrayList<Fragment> mAdded; //
ArrayList<Fragment> mActive; //
What's the difference of the two? and in what cases a Fragment will be in mAdded while not in mActive?
mAdded:
Contains fragments that have been added and not removed or detached from the activity.
These fragments are privileged in the sense that they can:
Respond to events such as:
low memory events
configuration changes
Display custom menus and respond to menu item selections.
mActive:
A superset of mAdded that includes all fragments referenced by any FragmentTransaction objects in the backstack.
Fragments that are NOT in mAdded will not be able to respond to events or display custom menus.
What events modify these two fragment lists?
mAdded
a fragment is added to this list if the fragment is added to the activity.
a fragment is removed from this list if:
the fragment is removed from the activity.
the fragment is detached from the activity.
mActive
a fragment is added to this list if the fragment is added to the activity.
a fragment is removed from this list ONLY under the following two scenarios:
it has been removed from the activity and is NOT in the backstack.
a transaction is popped off the backstack and either an add or replace operation is reversed on a fragment that is now no longer referenced by the backstack.
Conclusion
mAdded is a list of fragments that the are alive in a sense, while the mActive list is a complete list of all fragments that are still tied to the activity. mActive contains all living fragments (mAdded) and freeze dried fragments (sitting on the backstack waiting to be resuscitated via backStackRecord.popFromBackStack().
Continuing with the analogy of living and cryogenically preserved entities: as activities execute callbacks like onConfigurationChanged() or onLowMemory(), the only fragments that really care about being passed the opportunity to respond to these events are the live ones.
So you'll see in FragmentManagerImpl that the callback is only looking at the mAdded or living fragments.
fragmentManager.dispatchLowMemory() is called by activity.onLowMemory().
public void dispatchLowMemory() {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null) {
f.performLowMemory();
}
}
}
}
I don't know if you're still looking for an answer, but I found some clues about mActive and mAdded.
I found this in the source code of FragmentManager:
public void addFragment(Fragment fragment, boolean moveToStateNow) {
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
mAdded.add(fragment);
fragment.mAdded = true;
fragment.mRemoving = false;
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
if (moveToStateNow) {
moveToState(fragment);
}
}
}
The point is around the fifth line we call makeActive(fragment);.
This method calls mActive.add(fragment) so
mActive is incremented each time you call fragmentManager.addFragment()
mAdded is incremented when you call fragmentManager.addFragment() AND when (!fragment.mDetached) is true (look the code above)
So my guess is mAdded contains only the fragments which are attached to the activity and mActive contains the initialized fragments.
But I didn't look deep enough to be sure of my statement ...
I hope this helps
I have been using jfeinstein's SlidingMenu. I am currently trying to find if a certain fragment is visible to the user. I first tried:
if(mainfrag.isVisible()){
Log.d("Frag","Main is visible");
}else{
Log.d("Frag","Main is NOT visible");
}
Which always printed that the fragment was NOT visible. I then tried:
android.support.v4.app.FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
Log.d("Frag","CurFrag: "+fragmentManager.findFragmentById(R.id.content_frame).toString());
MainFragment mf = new MainFragment();
if(fragmentManager.findFragmentById(R.id.content_frame) == mf){
Log.d("Frag","This is Main");
}else{
Log.d("Frag","This is NOT Main :(");
}
This prints
So I know that the findFragmentById will tell me the current fragment but I don't know how I can logically compare it so I can do things only if it is visible.
I have never dived into the details of SlidingMenu and couldn't tell you what's wrong in the first problem.
But in your second problem, you are comparing two different objects.
MainFragment mf = new MainFragment();
fragmentManager.findFragmentById(R.id.content_frame) == mf
Here you create a new MainFragment, and try to compare it with a old instance. It can never be true. When comparing Objects, the address are compared. It will only return true if they are the same objects.
If you just want to check the class of object, use the following code:
Fragment f = fragmentManager.findFragmentById(R.id.content_frame);
if(f instanceof MainFragment)
// code here.
Get the fragment by tag or by I'd
Get the fragment's view
Get window visibility on the view will provide visibility