Skip to content

4. How to use with Fragments

Gabor Varadi edited this page Jun 1, 2020 · 5 revisions

Rationale

Fragment backstack only contains operations, but does not expose what Fragments currently exist.

Also, the BackstackChangeListener only provides the fact that the backstack has changed, but it does not tell you either the previous state, nor the new state.

Using Simple-Stack, it is possible to keep track of what Fragments are currently active, and simplify navigation between them.

Typical usage

We can define a BaseFragment which allows us to retrieve the screen key from the Fragment's arguments bundle. This allows us to pass arguments in a type-safe manner, without any magic tricks like the safeargs Gradle plugin, or XML descriptors. Just regular @Parcelize data class.

class BaseFragment: Fragment() {
    fun <T: BaseKey> getKey(): T = requireArguments().getParcelable("KEY");
}

where

abstract class BaseKey: Parcelable {
    open val fragmentTag: String = toString()
}

and then

class HomeFragment: BaseFragment() {
    // ...
    @Override
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val key: HomeKey = getKey()
    }

As for how to get the key into the arguments bundle in a reliable way whenever Fragment navigation occurs, see the FragmentStateChanger in the following section.

Providing navigation

In the examples, the FragmentStateChanger allows us to:

  • remove all fragments that no longer exist
  • add the current fragment if it doesn't exist
  • show the current fragment if it already exists but is hidden
  • any fragments that still exist but are not shown are hidden

Using the FragmentTransaction's add()/remove(), show()/hide(), attach()/detach() methods, we can customize the behavior according to previous and new state.

public class FragmentStateChanger {
    private FragmentManager fragmentManager;
    private int containerId;

    public FragmentStateChanger(FragmentManager fragmentManager, int containerId) {
        this.fragmentManager = fragmentManager;
        this.containerId = containerId;
    }

    public void handleStateChange(StateChange stateChange) {
        fragmentManager.executePendingTransactions();

        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction().disallowAddToBackStack();
        if (stateChange.getDirection() == StateChange.FORWARD) {
            fragmentTransaction.setCustomAnimations(R.anim.slide_in_from_right, R.anim.slide_out_to_left, R.anim.slide_in_from_right, R.anim.slide_out_to_left);
        } else if (stateChange.getDirection() == StateChange.BACKWARD) {
            fragmentTransaction.setCustomAnimations(R.anim.slide_in_from_left, R.anim.slide_out_to_right, R.anim.slide_in_from_left, R.anim.slide_out_to_right);
        }

        List<Step5Screen> previousKeys = stateChange.getPreviousKeys();
        List<Step5Screen> newKeys = stateChange.getNewKeys();
        for (Step5Screen oldKey : previousKeys) {
            Fragment fragment = fragmentManager.findFragmentByTag(oldKey.getFragmentTag());
            if (fragment != null) {
                if (!newKeys.contains(oldKey)) {
                    fragmentTransaction.remove(fragment); // remove fragments not in backstack
                } else if (!fragment.isDetached()) {
                    fragmentTransaction.detach(fragment); // destroy view of fragment not top
                }
            }
        }
        for (Step5Screen newKey : newKeys) {
            Fragment fragment = fragmentManager.findFragmentByTag(newKey.getFragmentTag());
            if (newKey.equals(stateChange.topNewKey())) {
                if (fragment != null) {
                    if (fragment.isRemoving()) { // fragments are quirky, they die asynchronously. Ignore if they're still there.
                        fragmentTransaction.replace(containerId, newKey.createFragment(), newKey.getFragmentTag());
                    } else if (fragment.isDetached()) {
                        fragmentTransaction.attach(fragment); // show top fragment if already exists
                    }
                } else {
                    fragment = newKey.createFragment(); // create and add new top if did not exist
                    fragmentTransaction.add(containerId, fragment, newKey.getFragmentTag());
                }
            } else {
                if (fragment != null && !fragment.isDetached()) {
                    fragmentTransaction.detach(fragment); // fragment not on top should not be showing
                }
            }
        }
        fragmentTransaction.commitAllowingStateLoss();
    }
}

Clone this wiki locally