FragmentTransaction hide/show doesn't work sometimes
Asked Answered
H

3

11

I have an activity with bottom navigation tabs that are changing the fragments in it. When I click back and forth on those tabs, at some point it stops working. Code executes just fine as I put some logs in it. But the fragments aren't being switched.

Code is in kotlin but it's rather straight forward

fun showTabFragment(tag: String) {
        val currentFragment: Fragment? = supportFragmentManager.fragments?.lastOrNull()
        var fragment = supportFragmentManager.findFragmentByTag(tag)
        val fragmentExists = fragment != null
        if (fragment == null) {
            when (tag) {
                TAG_LOGBOOK -> fragment = LogbookFragment()
                TAG_RECIPES -> fragment = RecipesFragment()
                TAG_PROFILE -> fragment = ProfileFragment()
                else -> fragment = MeetingPlacesFragment()
            }
        }

        val transaction = supportFragmentManager.beginTransaction()

        if (currentFragment != null) {
            Log.i("jacek", "hiding " + currentFragment.javaClass.simpleName)
            transaction.hide(currentFragment)
        }

        if (fragmentExists) {
            Log.i("jacek", "showing " + fragment.javaClass.simpleName)
            transaction.show(fragment)
        } else {
            Log.i("jacek", "adding " + fragment.javaClass.simpleName)
            transaction.add(R.id.container, fragment, tag)
        }

        transaction.commit()
    }

The fragments are quite heavy. I will try with some lightweight ones, but still that shouldn't be a problem in my opinion. Is there anything else I could try?

I'm using the latest support library - 25.2.0 Also I'm not interested in replacing the fragments as the point is to add crossfade animation without recreating them

Haehaecceity answered 14/3, 2017 at 20:9 Comment(1)
Maybe there is a problem when you call hide() and then show() on the same fragments within the same transaction? Like when currentFragment refers to the same fragments as fragment, which certainly can happen here. Speaking about it, I think it is wrong to claim that last fragment in supportFragmentManager.fragments list will be the last shown fragment (rather most recently added one). You should either iterate through all fragments and search for one that has isVisible() as true or just store the last shown fragment tag and find it later.Heavenly
I
21

You need to reuse the same instance of a fragment that you wanted to hide or show.

private fun replaceFragment(fragment: Fragment) {
    supportFragmentManager.beginTransaction().apply {
        if (fragment.isAdded) {
            show(fragment)
        } else {
            add(R.id.fmFragmentContainer, fragment)
        }

        supportFragmentManager.fragments.forEach {
            if (it != fragment && it.isAdded) {
                hide(it)
            }
        }
    }.commit()
}
Involve answered 18/8, 2018 at 13:34 Comment(0)
C
6

@Ali's answer is good, yet imagine if you have 5 fragments. This is another way to show/hide your fragments:

    // in BaseFragment
    public abstract String getTAG();

    //in FragmentA, FragmentB and FragmentC
    public String getTAG(){
        return TAG;
    }

    //Activity containing the fragments
    //android.support.v4.app.Fragment;    
    private FragmentA fragmentA; //inherited BaseFragment
    private FragmentB fragmentB; //inherited BaseFragment
    private FragmentC fragmentC; //inherited BaseFragment
    private ConcurrentHashMap<String,BaseFragment> mapOfAddedFragments = new ConcurrentHashMap<>();


    /**
     * Displays fragment A
     */
    private void displayFragmentA() {
        displayFragment(fragmentA)
    }

    /**
     * Displays  fragment B
     */
    private void displayFragmentB() {
       displayFragment(fragmentB)
    }

    /**
     * Displays  fragment C
     */
    private void displayFragmentC() {
        displayFragment(fragmentC)
    }


     /**
     * Loads a fragment using show a fragment
     * @param fragment
     */
    private void displayFragment(BaseFragment fragment){
        if(!mapOfAddedFragments.containsKey(fragment.getTAG()))
            mapOfAddedFragments.put(fragment.getTAG(), fragment);

        showFragment(fragment.getTAG(), R.id.containerBody);
    }

    /**
     * Displays a fragment and hides all the other ones
     * @param fragmentTag is the tag of the fragment we want to display
     */
    private void showFragment(String fragmentTag, @IdRes int containerViewId){
        FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
        BaseFragment fragment = null;

        fragment = mapOfAddedFragments.get(fragmentTag);
        if(fragment != null) {
            if (fragment.isAdded())
                ft.show(fragment);
            else { //fragment needs to be added to the frame container
                ft.add(containerViewId, fragment, fragment.getTAG());
            }
        }
        else //the chosen fragment doesn't exist
            return;

        //we hide the other fragments
        for (ConcurrentHashMap.Entry<String, BaseFragment> entry : mapOfAddedFragments.entrySet()){
            if(!entry.getKey().equals(fragmentTag)){
                BaseFragment fragmentTemp = entry.getValue();
                // Hide the other fragments
                if(fragmentTemp != null)
                    if(fragmentTemp.isAdded())
                        ft.hide(fragmentTemp);
            }
        }

        //commit changes
        ft.commit();
    }

And to instantiate them you can do this in the onCreate() method of your activity:

//don't forget to get the .TAG elsewhere before using them here
    //never call them directly
    private void instantiateFragments(Bundle inState) {
        if (inState != null) {
            fragmentA = inState.containsKey(FragmentA.TAG) ?
                    (FragmentA) getSupportFragmentManager().getFragment(inState, FragmentA.TAG):
                    FragmentA.newInstance(FragmentA.TAG,"0");

            fragmentB = inState.containsKey(FragmentB.TAG) ?
                    (FragmentB) getSupportFragmentManager().getFragment(inState, FragmentB.TAG):
                    FragmentB.newInstance(FragmentB.TAG,"1");        

            fragmentc = inState.containsKey(FragmentC.TAG) ?
                    (FragmentC) getSupportFragmentManager().getFragment(inState, FragmentC.TAG):
                    FragmentC.newInstance(FragmentC.TAG,"2");         
        }
        else{
            fragmentA = FragmentA.newInstance(FragmentA.TAG,"0");
            fragmentB = FragmentB.newInstance(FragmentB.TAG,"1");
            fragmentc = FragmentC.newInstance(FragmentC.TAG,"2");
        }
    }

Edit according to Shujaat Ali Khan's question:

The BaseFragment extends support4 fragment:

public abstract class BaseFragment extends Fragment {
    public abstract String getTAG();
    //whatever we can add to be inherited
}

FragmentA for example:

public class FragmentA extends BaseFragment {
    // Store instance variables
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    public static final String TAG = "FragmentA";

    // newInstance constructor for creating fragment with arguments
    public static FragmentA newInstance(String param1, String param2) {
        FragmentA fragment = new FragmentA();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    // Store instance variables based on arguments passed
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    // Inflate the view for the fragment based on layout XML
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragmentA, container, false);
        return view;
    }

    //other lifecycle methods

    @Override
    public String getTAG() {
        return TAG;
    }
}

Finally the R.id.containerBody is the id of a FrameLayout containing the fragments in the activity containing these fragments.

Caril answered 10/10, 2017 at 21:33 Comment(2)
Can you please show the implementation of, at least, FragmentA class? As I am a beginner so I am confused how NewInstance() methods are defined.Finnigan
@ShujaatAliKhan I just edited the answer to add an example.Caril
R
5

The problem here is even though you're hiding "current" fragment, there are other fragments loaded in the memory and that gives inconsistent behaviour.

You should be able to fix this by hiding all the fragment except the fragment you want to show.

Thanks to this answer. Show hide fragment in android

eg:

private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    fragmentA = FragmentA.newInstance();
    fragmentB = FragmentB.newInstance();
    fragmentC = FragmentC.newInstance();

}

protected void displayFragmentA() {

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    if (fragmentA.isAdded()) { 
        ft.show(fragmentA);
    } else { 
        ft.add(R.id.fragement_container, fragmentA);
    }

    if (fragmentB.isAdded()) { ft.hide(fragmentB); }

    if (fragmentC.isAdded()) { ft.hide(fragmentC); }

    ft.commit();
}

Similarly you will have to write functions for displayFragmentB() and displayFragmentC()

Reunionist answered 5/5, 2017 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.