Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: keep Fragment running

Is it possible to keep a Fragment running while I call replace from FragmentManager to open a new Fragment?

Basically I don't want to pause a Fragment when navigating (via replace method) to another Fragment.

Is it possible? Or the correct approach is, always, instantiate a new Fragment every time I need to open it and restore its previous state?

Thanks!

like image 335
igorjrr Avatar asked Aug 06 '14 02:08

igorjrr


People also ask

Can we call fragment without activity in Android?

Android app must have an Activity or FragmentActivity that handles the fragment. Fragment can't be initiated without Activity or FragmentActivity.

How can I maintain fragment state when added to the back stack?

Solution: Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.

Are fragments still used in Android?

A Fragment is a combination of an XML layout file and a java class much like an Activity . Using the support library, fragments are supported back to all relevant Android versions.

Are Android fragments deprecated?

Register and create a developer profile to keep track of sessions you're interested in by saving them to My I/O.


1 Answers

FragmentManger replace method will destroy the previous fragment completely, So in each transaction onDestroyView(), onDestroy() and onDetach() will get called on previous fragment. If you want to keep your fragment running you can instead use FragmentManger hide() and show() methods! It hides and shows the fragments without destroying them.

so first add both fragments to fragment manager and also hide the second fragment.

        fragmentManager.beginTransaction()
            .add(R.id.new_card_container, FragmentA)
            .add(R.id.new_card_container,FragmentB)
            .hide(FragmentB)
            .commit();

Note that you can only call show() on hidden fragment. So here you can't call show() on FragmentA but it's not a problem because by hiding and showing FragmentB you can get replacement effect you want.

And here is a method to go back and forth between your fragments.

public void showOtherFragment() {

    if(FragmentB.isHidden()){
        fragmentManager.beginTransaction()
                .show(FragmentB).commit();

    } else {
        fragmentManager.beginTransaction()
                .hide(FragmentB).commit();
    }
}

Now if you put log message in fragment callback method you will see there is no destruction (except for screen orientation change!), even view will not get destroyed since onDistroyView doesn't get called.

There is only one problem and that is, first time when application starts onCreateView() method get called one time for each fragment (and it should be!) but when the orientation changes onCreateView() gets called twice for each fragment and that's because fragments once created as usual and once because of there attachment to FragmentManger (saved on bundle object) To avoid that you have two options 1) detach fragments in onSaveInstaneState() callback.

@Override
protected void onSaveInstanceState(Bundle outState) {
    fragmentManager.beginTransaction()
            .detach(FragmentA)
            .detach(FragmentB)
            .commit();

    super.onSaveInstanceState(outState);
}

It's working but view state will not get updated automatically, for example if you have a EditText its text will erase each time orientation change happens. of course you can fix this simply by saving states in the fragment but you don't have to if you use the second option!

first i save a Boolean value in onSaveInstaneState() method to remember witch fragment is shown.

@Override
protected void onSaveInstanceState(Bundle outState) {
    boolean isFragAVisible = true;
    if(!FragmentB.isHidden())
        isFragAVisible = false;

    outState.putBoolean("isFragAVisible",isFragAVisible);

    super.onSaveInstanceState(outState);
}

now in activity onCreate method i check to see if savedInstanceState == null. if yes do as usual if not activity is created for second time. so fragment manager already contains the fragments. So instead i'm getting a reference to my fragments from fragment manager. also i make sure correct fragment is shown since its not recovered automatically.

    fragmentManager = getFragmentManager();

    if(savedInstanceState == null){

        FragmentA = new FragmentA();
        FragmentB = new FragmentB();
        fragmentManager.beginTransaction()
                .add(R.id.new_card_container, FragmentA, "fragA")
                .add(R.id.new_card_container, FragmentB, "fragB")
                .hide(FragmentB)
                .commit();

    } else {
        FragmentA = (FragmentA) fragmentManager.findFragmentByTag("fragA");
        FragmentB = (FragmentB) fragmentManager.findFragmentByTag("fragB");

        boolean isFragAVisible = savedInstanceState.getBoolean("isFragAVisible");
        if(isFragAVisible)
            fragmentManager.beginTransaction()
                    .hide(FragmentB)
                    .commit();
        else
            fragmentManager.beginTransaction()
                    .hide(FragmetA) //only if using transaction animation
                    .commit();
    }

By now your fragment will work perfectly if are not using transaction animation. If you do, you also need to show and hide FragmentA. So when you want to show FragmentB first hide FragmentA then show FragmentB (in the same transaction) and when you want to hide FragmentB hide it first and also show FragmentA (again in the same transaction). Here is my code for card flip animation (downloaded from developer.goodle.com)

public void flipCard(String direction) {
    int animationEnter, animationLeave;
    if(direction == "left"){
        animationEnter = R.animator.card_flip_right_in;
        animationLeave = R.animator.card_flip_right_out;
    } else {
        animationEnter = R.animator.card_flip_left_in;
        animationLeave = R.animator.card_flip_left_out;
    }

    if(cardBack.isHidden()){
        fragmentManager.beginTransaction()
                .setCustomAnimations(animationEnter, animationLeave)
                .hide(cardFront)
                .show(cardBack)
                .commit();

    } else {
        fragmentManager.beginTransaction()
                .setCustomAnimations(animationEnter,animationLeave)
                .hide(cardBack)
                .show(cardFront)
                .commit();
    }
}
like image 98
Alireza A. Ahmadi Avatar answered Sep 18 '22 16:09

Alireza A. Ahmadi