Proper/Best Way to Handle Android Fragment Transaction Issue - java

In my open source Android app, an issue has been discovered where a specific fragment will come into view on top of another fragment or crash the app in a certain situation.
The issue on GitHub if you want to see more information and example screenshots:
https://github.com/rpi-mobile/RPIMobile-Android/issues/31
I have pinned down the reason, but want to know which of the methods in the android.support.v4.app package to use to resolve the issue.
In MainActivity.java, is the code for the navigation drawer that uses FragmentTransaction.replace() to switch fragments.
The issue arises because in MapFragment, I use:
ViewMapFragment vmf = new ViewMapFragment();
FragmentTransaction ft = getSherlockActivity().getSupportFragmentManager().beginTransaction();
ft.addToBackStack(null);
ft.replace(R.id.content_frame, vmf);
ft.commit();
And in ViewMapFragment's onDestroyView():
FragmentManager fm = getSherlockActivity().getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(fm.findFragmentById(R.id.mapview));
ft.commit();
The onDestroyView() properly removes the ViewMapFragment from view, but if you have it in view and use the navigation drawer to change to a different fragment, MapFragment is still on the back stack.
So, for my questions:
1) How do I check if a particular fragment is on the back stack before attempting to remove/replace it, or will the app not crash if you attempt when nothing is there (i.e. not checking)? E.g. calling popBackStack() when there is nothing on the back stack.
2) Should I use FragmentManager class methods to attempt to remove the MapFragment from the back stack or should I use FragmentTransaction methods? Pros vs cons?
3) What is the difference in the UI between popBackStack() and popBackStackImmediate()? Does the user see some glitchy transitions?

According to the documentation of FragmentTransaction, when you invoke addToBackStack method it just remembers what actions you perform in that transaction. When popBackStack method is invoked it will reverse those actions and execute them.
So, what happens:
When we go from MapFragment to ViewMapFragment, FragmentManager remembers removing MapFragment and adding ViewMapFragment operations.
Then we go to any other fragment using Navigation Drawer, which causes ViewMapFragment removing operation, and after that it adds fragment selected from Drawer.
Finally, when we press Back button, popBackStackImmediate is invoked and FragmentManager executes reversed operations: ViewMapFragment is removed (actually it is removed already) and MapFragment is added. And here is the problem occurs - the selected fragment is still there, so we have two fragments on the screen at the same time.
There is several options to handle such a situation:
Simply add Navigation Drawer operations to back stack.
Clear back stack every time when fragment switching is started by Navigation Drawer.
To clear back stack you can use this code (related question):
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
If you put this line in the beginning of your selectItem method the bug will be fixed.
And your questions:
You can check whether a fragment is in a back stack by using FragmentManager.findFragmentByTag method. See example in the end of my answer.
An entry of back stack technically can contain several fragments, and I don't think there is a way to remove single fragment from it. Instead of removing you can just clear back stack (please, refer this question).
The main difference is that popBackStack will give you a chance to do something, before it actually starts popping process (it will be started when application returns to it's event loop).
Example. For instance, we have added a fragment with tag "fragment-1":
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frame, new TestFragment(), "fragment-1")
.commit();
Then, we put it into a back stack and replace it with another fragment:
getSupportFragmentManager()
.beginTransaction()
.addToBackStack(null)
.replace(R.id.frame, new TestFragment(), "another-fragment")
.commit();
At this point getSupportFragmentManager().findFragmentByTag("fragment-1") returns our first fragment (it gets it from a back stack entry). Now we can check whether this fragment is added to its activity by isAdded method - if it returns false, then we can make an assumption that the fragment is in a back stack.

Related

How to switch between fragments forwards and backwards correctly?

I'm currently creating an activity containing a few stages.
It is supposed to start at 'stage1' and when clicking next proceed to the next stage and when clicking back go to the previous stage.
I've tried using FragmentTransaction and replace an empty container with the fragment that supposed to be every time, and added the fragments that I've passed to the back stack. It worked at the beginning, After adding a few stages it got corrupted. Suddenly when trying to move from 'stage4' to 'stage3' it passed 'stage3' and went directly to 'stage2'.
private void changeFragment(Fragment fragment, String tag) {
FragmentTransaction ft = fm.beginTransaction().replace(R.id.frame, fragment);
ft.addToBackStack(tag);
ft.commit();
fm.executePendingTransactions();
}
That's what I've tried to use to change between the fragments but it concluded in failure.
When I tried using ViewPager instead just without the tabs. Creating the movement as I wanted, when I set the currentItem if it pointed to 'stage2' it went to 'stage3'.
In conclusion, all I want to get in the end is an activity which passes through a number of fragments(forwards and backwards) without any issues.
Thanks!

Move from Activity to Fragment when button clicked

I used this code to move from Activity to Fragment when button clicked but when I run this code I find Activity is still in background.
TopicsFragment fragment = new TopicsFragment();
android.support.v4.app.FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.welcome_id, fragment);
transaction.commit();
First of all, you need to understand what Activity and Fragments are. You cannot move from Activity to Fragment.
You can inflate a Fragment or Fragments into an Activity. Fragments cannot be independent from an activity.
A fragment must always be hosted in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle. For
example, when the activity is paused, so are all fragments in it, and
when the activity is destroyed, so are all fragments. However, while
an activity is running (it is in the resumed lifecycle state), you can
manipulate each fragment independently, such as add or remove them.
When you perform such a fragment transaction, you can also add it to a
back stack that's managed by the activity—each back stack entry in the
activity is a record of the fragment transaction that occurred. The
back stack allows the user to reverse a fragment transaction (navigate
backwards), by pressing the Back button.
Source: https://developer.android.com/guide/components/fragments
If you don't want to see the activity after entering into fragment, you could extend from DialogFragment and make it fullscreen, there are many examples, google it using "android fullscreen dialogfragment".
and here is DialogFragment doc.

Android - onBackPressed has no effect from fragment

I have one Activity with a viewpager in it and also a frameLayout which is used as a container for fragments that should be drawn above this viewpager.
When I click a button of my Activity a fragment is shown in that framelayout container. On a press in the navigation bar I can close this fragment. All these things work fine when called from the activity itself.
I have now the situation that on a specific action from one of the fragments in the viewpager I invoke a callback which the main activity implements and then shows a fragment in the frame layout.
But here now comes the problem. When I want to navigate back or rather want to close the fragment like I described before, nothing happens.
So in conclusion:
"Fragment show to container" invoked from Activity directly -> I can navigate back from the fragment by getActivity().onBackpressed()
"Fragment show to container" invoke from fragment in viewpager and then delegated to activity -> The navigation back or getActivity().onBackpressed() has no effect.
So my question is: How can I close the fragment in the container even when I call it from one the fragments inside the viewpager of the activity?
Probably you are adding a fragment to the backstack, so onBackPressed you should pop last fragment from the backstack.
Something like that:
FragmentManager fm = activity.getFragmentManager();
fm.popBackStackImmediate();
Meanwhile I found the problem..
Due to the fact that I am running some kind of update loop, I added the fragments multiple times to the backstack but didn't realize it because the fragment itself didn't change, the new instances just got added to the backstack. So when I called the onBackpressed() method the instances got removed from the backstack but because there were like 200 of them nothing happened.

Bring Fragment to top of stack

When adding activities to the stack, I can do something like this suggests:
How to bring an activity to foreground (top of stack)?
However, I have a Navigation Drawer that uses fragments. I add these fragments to my back stack via the below code:
FragmentTransaction transaction = activity.getFragmentManager().beginTransaction();
transaction.replace(R.id.main_fragment, new EntryFragment());
transaction.addToBackStack(activity.mTitle.toString());
transaction.commit();
The problem is, I now need to take a fragment that is already part of the back stack and bring it to the top, dropping all fragments currently above it out of the stack. Essentially what FLAG_ACTIVITY_REORDER_TO_FRONT and FLAG_ACTIVITY_CLEAR_TOP flags would do when using activities.
How do I accomplish this with fragments?
You can use the following method to return to the instance of the Fragment on the backstack:
activity.getFragmentManager().popBackStackImmediate(tag, 0);
Note, that in your FragmentTransaction you will need to define a unique tag for each Fragment you commit to the backstack and retrieve that tag to return to the fragment here.

Android Fragments not being recreated on Activity Restart

I have an Activity which contains a couple of nested Fragments. I have been having a lot of trouble handling orientation changes. Something is not correct when the Activity tries to recreate itself which causes the app to crash. If I try to debug the code during an orientation change the app crashes without any messages in the logcat.
My problem seems to be the the runtime is trying to recreate the fragments in my Activity when I don't want it to. None of the fragments have setRetainInstance(true) so they shouldn't be getting kept.
I have added logging to the onCreate and onDestroy methods of the Activity and Fragments. When the app first runs I get the following output:
Activity onCreate
Fragment1 onCreate
Fragment1 onCreateViewStart
Fragment1 onCreateViewEnd
Fragment2 onCreate
Fragment2 onCreateViewStart
Fragment2 onCreateViewEnd
I then rotate the device and get the following:
Fragment1 onDestroy
Fragment2 onDestroy
Activity onDestroy
Fragment1 onCreate
Fragment2 onCreate
Activity onCreate
Fragment1 onCreateViewStart
Fragment1 onCreateViewEnd
Fragment2 onCreateViewStart
Fragment2 onCreateViewEnd
Fragment2 onDestroy
Fragment2 onCreate
After the last message above the app crashes with the following exception message:
E/FragmentManager(32258): No view found for id 0x7f0a0047 (com.appName:id/fragmentHolder) for fragment Fragment2{6529b910 #6 id=0x7f0a0047 16002}
Why are my fragments being recreated automatically? Is there anything I can do to force the activity to reload as if the app was being started for the first time when the orientation changes? Is this not how the life cycle is supposed to work in the first place?
I can add any code if requested.
The FragmentManager is responsible for managing the fragments and
adding them to the activity's view hierarchy.
The FragmentManager handles two things:
A list of fragments.
back stack of fragment transactions.
To add, remove, attach, detach or replace fragments in the fragment list you use Fragment Transactions.
When you ask the FragmentManager for a fragment using findFragmentById() or findFragmentByTag(), if the fragment is
already in the list, the FragmentManager will return it. Then you can
use it.
Knowing that. If there are fragments added to the FragmentManager's list when the orientation change happens, those fragments are going to be recreated automatically in order to recreate the view hierarchy as it was before the orientation change. If you don't want a fragment to be recreated, remove it from the list using a fragment transaction.
You know how if you background an app and then reopen it? The Activity instances are still there with the Bundle savedInstanceState populated with data. This means that the OS is recreating your app from a serialised state rather than from scratch.
What you need to do in the case where savedInstanceState is not null is look use your FragmentManager to findFragmentByTag.

Categories