Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FragmentTransaction hide/show doesn't work sometimes

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

like image 689
Jacek Kwiecień Avatar asked Mar 14 '17 20:03

Jacek Kwiecień


People also ask

How do you know if a fragment is visible?

fragment:fragment:1.1. 0 you can just use onPause and onResume callbacks to determine which fragment is currently visible for the user. onResume callback is called when fragment became visible and onPause when it stops to be visible.

How do you hide a fragment?

Showing and hiding fragment's viewsUse the FragmentTransaction methods show() and hide() to show and hide the view of fragments that have been added to a container.

How do you identify a fragment tag?

findFragmentById(R. id. fragment_container); Alternatively, you can assign a unique tag to a fragment and get a reference using findFragmentByTag() .

What are fragments in Android?

A Fragment represents a reusable portion of your app's UI. A fragment defines and manages its own layout, has its own lifecycle, and can handle its own input events. Fragments cannot live on their own--they must be hosted by an activity or another fragment.


2 Answers

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()
}
like image 172
dtunctuncer Avatar answered Oct 25 '22 02:10

dtunctuncer


@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.

like image 45
Maxime Claude Avatar answered Oct 25 '22 01:10

Maxime Claude