Detaching/reattaching fragments leaves black screen on the container - android

Imagine we are starting the app, we add the FragmentA on the backstack and according to this snippet (see below), the fragment is added to the layout, so no previous fragment is on the backstack or in the container.
When I go to Quick settings -> turn on Airplane mode (I know it might not have something to do with it, but thats the only repro), I get blank screen when I when I try to move to FragmentB.
Basically, FragmentA is still on the backstack, when FragmentB is added to the I detach FragmentA from the UI (frag: remove is false) , but it is still on the backstack.
public static void showFragment(FragmentActivity activity, Fragment f, boolean remove, boolean addToBackStack){
Fragment lastFragment = activity.getSupportFragmentManager().findFragmentById(R.id.content_frame);
FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
if (lastFragment != null) {
if(!remove){
ft.detach(lastFragment);
Log.i("FragmentUtil", "Detaching fragment "+lastFragment.getClass().getSimpleName());
}else{
Log.i("FragmentUtil", "Removing fragment "+lastFragment.getClass().getSimpleName());
ft.remove(lastFragment);
}
}
if (f != null && !f.isAdded() && !f.isDetached()) {
ft.add(R.id.content_frame, f, f.getClass().getName());
Log.i("FragmentUtil", "Adding fragment "+f.getClass().getSimpleName());
if(addToBackStack)
ft.addToBackStack(f.getClass().getName());
} else {
Log.i("FragmentUtil", "Atttaching fragment "+f.getClass().getSimpleName());
ft.attach(f);
}
ft.commit();
}
Both fragments are shown with remove = false and addToBackStack = true;
TL;DR: Screen gets black when I have FragmentA shown and turn on Airplane mode and try to add FragmentB.

Related

How to get current fragment in android using ById or ByTag

In my main activity, i have a framelayout which is used to display fragments
now imagine, i have 3 fragment objects :
Fragment1 f1 = new Fragment1();
Fragment2 f2 = new Fragment2();
Fragment3 f3 = new Fragment3();
and i set this fragment using :
fragTrans.replace(android.R.id.content, f1);
fragTrans.addToBackStack(null);
fragTrans.commit();
now at different point of times, the app might have one of the three fragments in the framelayout (number of fragments might vary).
So, at some point if i want to identify which of the fragments if being currently displayed in the framelayout, how can i do that ?
My purpose :
the fragment2 has say 2 states, where boolean state can be either true or false, if the state is true, and the back button is pressed i want to do something and set state to false and the if the back is pressed i'll call super.onBackPressed() but if it is set to true and if currently fragment 3 is visible, first i want to go to fragment 2, then change the state to false and then super.onBackPressed()
so that will make it piece of cake if i can identify which fragment is currently visible
Try this
Fragment fragment = (Fragment)supportFragmentManager.findFragmentById(R.id.fragment_container)
Your Solution is here
1. First of you replace the fragment replace line with this line.
fragTrans.replace(android.R.id.content, f1,f1.getClass.getName());
Here f1.getClass,getName() is key of current Fragment. it Gives you name of fragment which is replace or current replace fragment.
FragmentManager manager = getSupportFragmentManager();
Fragment1 fragment1 = (Fragment1) manager.findFragmentByTag(Fragment1.class.getName());
Fragment2 fragment2 = (Fragment2) manager.findFragmentByTag(Fragment2.class.getName());
Fragment3 fragment3 = (Fragment3) manager.findFragmentByTag(Fragment3.class.getName());
if (fragment1 != null) {
Log.e(TAG, "Current fragment is Fragment1");
}else if(fragment2 != null) {
Log.e(TAG, "Current fragment is Fragment2");
}else if(fragment3 != null) {
Log.e(TAG, "Current fragment is Fragment3");
} else {
Log.e(TAG, "Fragment 1-2-3 is null");
}
If you do something on first time onBackPress Follow the code below,
#Override
public void onBackPressed() {
if (isBackPress) { //Default is false
super.onBackPressed();
} else {
// On First time click do something
isBackPress=true;
}
}

Fragment backstack and toggle

I'm having an special use case where I need to switch between two fragments. The issue I'm having is that for the second fragment I need to persist it's state, and the only thing that seems to be working for that is to add it to the BackStack.
I rely on the support fragment manager to replace the fragments:
public void toggle() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof FragmentB && null != fragmentA) {
// fragment B is visible - we should show fragment A
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
R.anim.frag_fade_in, R.anim.frag_fade_out)
.replace(R.id.fragment_container, fragmentA)
.commit();
} else if (fragment instanceof FragmentA && null != fragmentB) {
// fragment A is visible - we should show fragment B
boolean isRestored = false;
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentB = (FragmentB) fragment;
isRestored = true;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in,
R.anim.frag_fade_out,
R.anim.frag_fade_in,
R.anim.frag_fade_out);
transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
if(!isRestored){
transaction.addToBackStack(TAG_FRAG_B)
}
transaction.commit();
} else {
// Just pop any fragments that were added - usually we won't get in here
getSupportFragmentManager().popBackStack();
}
}
This in combination with the onBackPressed() override:
#Override
public void onBackPressed() {
if (isCurrentFragmentB()) {
toggle();
} else {
// Back key was pressed and we are on fragment A - at this state we simply want to go back to the
// previous section
super.onBackPressed();
}
}
Using this implementation I make sure I reuse fragment B and keep it's state so that it doesn't look like it is created from scratch each time. I also make sure that when I go back, I can go only from fragment B to A and not from fragment A to B.
The issue I encountered is that when super.onBackPressed(); is called and more than one fragment was added(replaced actually, as I want only one active fragment at a time) through the fragment manager, it will throw an exception:
java.lang.IllegalStateException: Fragment already added: FragmentA{af9c26b #0 id=0x7f0e00d3}
This is happening only when the active fragment is FragmentA. I have a suspicion that this is because of the BackStack implementation, but as I've said, I only want the second one to be persisted.
How can I fix this? I am missing something?
I have managed to implement an work-around for this, although it is a little hacky.
Because I need to keep the state of FragmentB, I am forced to add it to the BackStack, but this will actually affect what transition is reversed when onBackPressed() is called.
To avoid this, I had to update the logic for the back press and manually handle that case
#Override
public void onBackPressed() {
if (isCurrentFragmentB()) {
toggle();
} else if (isCurrentFragmentA()) {
getSupportFragmentManager().popBackStackImmediate(TAG_FRAG_A, FragmentManager.POP_BACK_STACK_INCLUSIVE);
// Special case - because we added the fragment B to the BackStack in order to easily resume it's state,
// this will fail as it will actually try to add fragment A again to the fragment manager (it
// will try to reverse the last transaction)
super.finish();
} else {
// Usual flow - let the OS decide what to do
super.onBackPressed();
}
}
Also, I've optimized the toggle method a little bit:
public void toggle() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
#SuppressLint("CommitTransaction") FragmentTransaction transaction =
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.frag_fade_in, R.anim.frag_fade_out,
R.anim.frag_fade_in, R.anim.frag_fade_out);
if (fragment instanceof FragmentB && null != fragmentA) {
// fragment B is visible - we should show fragment A
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_A);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentA = (FragmentA) fragment;
}
// Replace current fragment with fragment A and commit the transaction
transaction.replace(R.id.fragment_container, fragmentA, TAG_FRAG_A).commit();
} else if (fragment instanceof FragmentA && null != fragmentB) {
// fragment A is visible - we should show fragment B
fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_B);
if (null != fragment) {
// Restore fragment state from the BackStack
fragmentB = (FragmentB) fragment;
}
// Replace current fragment with fragment B
transaction.replace(R.id.fragment_container, fragmentB, TAG_FRAG_B);
if (null == fragment) {
// No entry of the fragment B in the BackStack, we want to add it for future uses
transaction.addToBackStack(TAG_FRAG_B);
}
// Commit the transaction
transaction.commit();
} else {
// Just pop any fragments that were added - usually we won't get in here
getSupportFragmentManager().popBackStack();
}
}
I hope this can help others which need an similar flow.
PS: The fragment I want to persist is SupportMapFragment, so that my map isn't always redrawn, re-centered and populated with data every time I want to show it.

Fragments and backstage sometimes get messed up

I have an activity with a container for fragments and a NavigationDrawer. If I select an item in the drawer, I call updatePage(index).
I also call updatePage(0) if I create the activity and the savedInstanceState == null to init my activity.
One fragment has a sub fragment and therefor it just replaces itself by the subfragment and adds the subfragment to the backstack, so that the user can navigate back to the previous fragment. In this fragment I call following code directly:
#Override
public void onClick(View view)
{
Event event = (Event)view.getTag();
FragmentManager fm = getActivity().getSupportFragmentManager();
GamesFragment f = new GamesFragment();
Bundle bundle = new Bundle();
bundle.putParcelable(GamesFragment.KEY_EVENT, event);
f.setArguments(bundle);
FragmentTransaction ft = fm.beginTransaction()
.replace(R.id.frame_container, f, f.getClass().getName())
.addToBackStack(null);
ft.commit();
}
Why does sometimes the removing of the old fragment not work? I get overlaying fragments, but only sometimes.
My activities updatePage function looks like following:
private void updatePage(int drawerSelection)
{
mDrawer.closeDrawer();
Fragment f = null;
FragmentManager fm = getSupportFragmentManager();
switch (drawerSelection)
{
case 0:
f = fm.findFragmentByTag(HomeFragment.class.getName());
if (f == null)
f = new HomeFragment();
break;
case 1:
f = fm.findFragmentByTag(EventFragment.class.getName());
if (f == null)
f = new EventFragment();
break;
default:
break;
}
if (f != null && !f.isAdded())
{
// SOLUTION:
// Backstack clearen
// fm.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.frame_container, f, f.getClass().getName());
ft.commit();
}
}
I saw such situation few times and I bet that it can happen after replacing with addToBackStack. Easiest fix is to add background to fragment layout. From android doc:
Note: When you remove or replace a fragment and add the transaction to the back stack, the fragment that is removed is stopped (not destroyed). If the user navigates back to restore the fragment, it restarts. If you do not add the transaction to the back stack, then the fragment is destroyed when removed or replaced.
This view is not redrawed entirely just overdrawed and if second fragment doesn't has background or view container redraw you will get effect as described. That's my theory :>. Sometimes Android documentation is not as clear as we would like it to be.
The problem is following use case:
Going to the EventFragment from the GameFragment pushes the "remove(EventFragment).add(GameFragment)" transaction to the backstack. If I now press the back button, this transaction will be undone and everything is fine. The backstack is empty again and everything works. BUT, if I don't press the back button, but change to another fragment through the menu, the backstack does still have the above mentioned transaction. Pressing back will now try to undo this transaction... It will just readd the EventFragment before the code in my menu click handler adds the fragment from the menu... This is how it could happen...
Easy solution, if I only want one backstack for every menu entry and want to delete the backstack, if I select another area of my app in the menu is, to clear the backstack before I go to another area through the menu...
So adding ' fm.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);' before replacing the the current fragment will solve the problem (I added this solution as a comment to my main post)

Back from third to first fragment in android

I have a problem with android fragment backstack. This is my situation.
// I can't post images yet, so I passed a link to it.
As you can see, I want to go from Fragment 1 to Fragment 2 and at the end to Fragment 3, but when I press back button at Fragment 3, I want to back to Fragment 1.
I do this like I describe on pic, but when I press Back Button nothing happen and when I press it second time, the app is closing.
My BackStack looks as expected, I have on it only "Main" entry.
Also, when I add to backstack Fragment 2 I can back normally from Frag3 to Frag2 to Frag1 (but this is not what I want).
//Edit:
First:
I debug my app a little and I notice that when I press back button, Fragment is poped out from backstack and his lifecycle methods were invoked, but current fragment (Fragment 3) do nothing (I logged his onPause and onStop methods and they weren't invoked). Maybe this is a problem?
Second:
I found a solution that I implement onBackStackChange listener and in onBackStacChange method I simply replace Fragment3 with Fragment1. This works, but is it correct?
do it using....
FragmentManager fmManager = activity.getSupportFragmentManager();
if (fmManager.getBackStackEntryCount() > 0) { fmManager.popBackStack(fmManager.getBackStackEntryAt(fmManager.getBackStackEntryCount()-2).getId(), fmManager.POP_BACK_STACK_INCLUSIVE); }
-2 is because you want to go two 2 fragment step back
#Override
public void onPageScrollStateChanged(int state) {
int currentPage = mChannelPager.getCurrentItem();
if (currentPage == mChannelsList.size() - 1 || currentPage == 0) {
previousState = currentState;
currentState = state;
if (previousState == 1 && currentState == 0) {
mChannelPager.setCurrentItem((currentPage == 0 ? mChannelsList.size() - 1 : 0), false);
}
}
}
});
Write this code in your onPageChangeListner's onPageScrollStateChanged(int state)
1) Add First Fragment using below code
android.support.v4.app.FragmentManager fm = getActivity().getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft=fm.beginTransaction();
if (fm.findFragmentById(R.id.fragment_container) != null) {
ft.hide(fm.findFragmentById(R.id.fragment_container));
}
ft.add(R.id.fragment_container, new OneFragment(),OneFragment.class.getCanonicalName())
.addToBackStack(OneFragment.class.getCanonicalName()).commit();
2) Add Second Fragment From First fragment using below code
android.support.v4.app.FragmentManager fm = getActivity().getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft=fm.beginTransaction();
if (fm.findFragmentById(R.id.fragment_container) != null) {
ft.hide(fm.findFragmentById(R.id.fragment_container));
}
ft.add(R.id.fragment_container,new TwoFragment(),TwoFragment.class.getCanonicalName())
.addToBackStack(TwoFragment.class.getCanonicalName()).commit();
3) Add Third Fragment From Second fragment using below code
android.support.v4.app.FragmentManager fm = getActivity().getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft=fm.beginTransaction();
if (fm.findFragmentById(R.id.fragment_container) != null) {
ft.hide(fm.findFragmentById(R.id.fragment_container));
}
ft.add(R.id.fragment_container, new ThreeFragment(),ThreeFragment.class.getCanonicalName())
.addToBackStack(ThreeFragment.class.getCanonicalName()).commit();
4) onBackPressed() please write below code
#Override
public void onBackPressed() {
hideKeyboard(MainActivity.this);
Fragment currentFragment = this.getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (currentFragment.getClass().getName().equalsIgnoreCase(ThreeFragment.class.getName())) { // Using this code come from third fragment to first fragment
Fragment f = this.getSupportFragmentManager().findFragmentByTag(TwoFragment.class.getCanonicalName());
if (f != null) {
this.getSupportFragmentManager().popBackStackImmediate(f.getClass().getCanonicalName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}else {
super.onBackPressed();
}
}

In Fragment on back button pressed Activity is blank

I have an Activity and many fragments inflated in same FrameLayout
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
example: mainActivity > any fragment (press back button) > activity is blank.
In onCreate:
layout = (FrameLayout)findViewById(R.id.content_frame);
layout.setVisibility(View.GONE);
When I start a fragment:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, profileFragment);
ft.addToBackStack(null);
ft.commit();
layout.setVisibility(View.VISIBLE);
I suppose I need to make the frameLayout's visibility GONE again on back pressed, but how do I do this?
I tried onBackPressed and set layout.setVisibility(View.GONE); but I cannot go back through fragments, as I go directly to main page.
If you have more than one fragment been used in the activity or even if you have only one fragment then the first fragment should not have addToBackStack defined. Since this allows back navigation and prior to this fragment the empty activity layout will be displayed.
// fragmentTransaction.addToBackStack() // dont include this for your first fragment.
But for the other fragment you need to have this defined otherwise the back will not navigate to earlier screen (fragment) instead the application might shutdown.
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
}
else {
int fragments = getSupportFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
finish();
} else if (getFragmentManager().getBackStackEntryCount() > 1) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
To add a fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.layout_main, dashboardFragment, getString(R.string.title_dashboard))
.addToBackStack(getString(R.string.title_dashboard))
.commit();
Sorry for the late response.
You don't have to add ft.addToBackStack(null); while adding first fragment.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, profileFragment);
// ft.addToBackStack(null); --remove this line.
ft.commit();
// ... rest of code
If you want to track by the fragments you should override the onBackPressed method, like this
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() == 1) {
finish();
} else {
super.onBackPressed();
}
}
You can override onBackPressed and check to see if there is anything on the backstack.
#Override
public void onBackPressed() {
int fragments = getFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
// make layout invisible since last fragment will be removed
}
super.onBackPressed();
}
Just don't add the first fragment to back stack
Here is the Kotlin code that worked for me.
val ft = supportFragmentManager.beginTransaction().replace(container, frag)
if (!supportFragmentManager.fragments.isEmpty()) ft.addToBackStack(null)
ft.commit()
On a recent personal project, I solved this by not calling addToBackStack if the stack is empty.
// don't add the first fragment to the backstack
// otherwise, pressing back on that fragment will result in a blank screen
if (fragmentManager.getFragments() != null) {
transaction.addToBackStack(tag);
}
Here's my full implementation:
String tag = String.valueOf(mCurrentSectionId);
FragmentManager fragmentManager = mActivity.getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
// if the fragment exists then no need to create it, just pop back to it so
// that repeatedly toggling between fragments doesn't create a giant stack
fragmentManager.popBackStackImmediate(tag, 0);
} else {
// at this point, popping back to that fragment didn't happen
// So create a new one and then show it
fragment = createFragmentForSection(mCurrentSectionId);
FragmentTransaction transaction = fragmentManager.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(R.id.main_content, fragment, tag);
// don't add the first fragment to the backstack
// otherwise, pressing back on that fragment will result in a blank screen
if (fragmentManager.getFragments() != null) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
irscomp's solution works if you want to end activity when back button is pressed on first fragment. But if you want to track all fragments, and go back from one to another in back order, you add all fragments to stack with:
ft.addToBackStack(null);
and then, add this to the end of onCreate() to avoid blank screen in last back pressed; you can use getSupportFragmentManager() or getFragmentManager() depending on your API:
FragmentManager fm = getSupportFragmentManager();
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getSupportFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
Final words: I don't suggest you to use this solution, because if you go from fragment1 to fragment 2 and vice versa 10 times, when you press back button 10 times it will do it in back order which users will not want it.
Almost same as Goodlife's answer, but in Xamarin.Android way:
Load fragment (I wrote helper method for that, but it's not necessary):
public void LoadFragment(Activity activity, Fragment fragment, string fragmentTitle = "")
{
var fragmentManager = activity.FragmentManager;
var fragmentTransaction = fragmentManager.BeginTransaction();
fragmentTransaction.Replace(Resource.Id.mainContainer, fragment);
fragmentTransaction.AddToBackStack(fragmentTitle);
fragmentTransaction.Commit();
}
Back button (in MainActivity):
public override void OnBackPressed()
{
if (isNavDrawerOpen()) drawerLayout.CloseDrawers();
else
{
var backStackEntryCount = FragmentManager.BackStackEntryCount;
if (backStackEntryCount == 1) Finish();
else if (backStackEntryCount > 1) FragmentManager.PopBackStack();
else base.OnBackPressed();
}
}
And isNavDrawerOpen method:
bool isNavDrawerOpen()
{
return drawerLayout != null && drawerLayout.IsDrawerOpen(Android.Support.V4.View.GravityCompat.Start);
}
I still could not fix the issue through getBackStackEntryCount() and I solved my issue by making the main page a fragment too, so in the end I have an activity with a FrameLayout only; and all other fragments including the main page I inflate into that layout. This solved my issue.
I had the same problem when dealing with Firebase's Ui Login screen. When back button was pressed it left a blank screen.
To solve the problem I just called finish() in my onStop() method for said Activity. Worked like a charm.
If you have scenario like me where a list fragment opens another details fragment, and on back press you first need to show the list fragment and then get out the whole activity then, addToBackStack for all the fragment transactions.
and then on the activity, do like this (courtesy: #JRomero's answer, #MSaudi's comment)
#Override
public void onBackPressed() {
int fragments = getFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
// make layout invisible since last fragment will be removed
}
super.onBackPressed();
}
Just Comment or Remove transaction.addToBackStack(null) in your code.Below is code to change fragment in kotlin.
fun ChangeFragment(activity: MainActivity, fragment: Fragment) {
val transaction: FragmentTransaction =
activity.getSupportFragmentManager().beginTransaction()
transaction.replace(R.id.tabLayoutContainer, fragment)
transaction.commit()
}

Categories

Resources