Skip to content

3. Installation

Gabor Varadi edited this page Sep 2, 2018 · 6 revisions

The big picture

By default, you'd only need a Backstack instance and set a StateChanger to manipulate and navigate between your states. However, that wouldn't survive process death and wouldn't help with saving your viewstate - which is why generally, you'd need a BackstackManager instance.

But it's worth knowing that freezing/unfreezing the queue, surviving config change and process death; they need different lifecycle callbacks to work.

Therefore, two additional "utility classes" are provided that help with activity lifecycle integration of the BackstackManager.

These classes are the BackstackDelegate and the Navigator.

Installation

Navigator

If you're above API 11+ and you're not trying to use Simple-Stack as a replacement for the fragment backstack, then you most likely need Navigator.

Navigator creates a BackstackHost (retained fragment) internally, and survives config changes while listening to all required lifecycle callbacks.

The simplest integration for Navigator would look like this:

public class MainActivity
        extends AppCompatActivity {
    public static final String TAG = "MainActivity";

    @BindView(R.id.root)
    RelativeLayout root;

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

        Navigator.install(this, root, History.single(FirstKey.create()));
    }

    @Override
    public void onBackPressed() {
        if(!Navigator.onBackPressed(this)) {
            super.onBackPressed();
        }
    }
}

But there are additional means to configure() the Navigator, for example providing a custom key parceler (which can make your state be parcelable if by default it isn't), or a configured version of the DefaultStateChanger.

        val backstack = Navigator.configure()
            .setScopedServices(ServiceProvider())
            .setDeferredInitialization(true)
            .setStateChanger(this)
            .install(this, root, History.single(TasksKey()))
        backstackHolder.setBackstack(backstack)
        Navigator.executeDeferredInitialization(this)

You're not required to provide the DefaultStateChanger as a state changer, you can provide any StateChanger you like. The DefaultStateChanger is really just for convenience (primarily for the simplest case, to be honest) so that you don't need to implement swapping views and persisting/restoring their states manually. If you do, you can check the mvp-view sample where this is handled manually.

If you use the DefaultStateChanger, then your keys are expected to implement StateKey interface (which specifies a ViewChangeHandler - that determines how you animate your views; and also the layout resource for view inflation).

BackstackDelegate

The BackstackDelegate is the original lifecycle integration, where you have to delegate all callbacks manually. It ought to be used if you need fine-grained behavior (as in, you need to be able to create the backstack before super.onCreate() - as it is the case with fragments; or you need to manage multiple backstacks inside the same activity).

For example, this is for fragments.

    override fun onCreate(savedInstanceState: Bundle?) {
        backstackDelegate = BackstackDelegate() // for fragments, this is before `super.onCreate()`

        // configuration happens before onCreate
        backstackDelegate.setScopedServices(this, ServiceProvider()) 

        backstackDelegate.onCreate(savedInstanceState, //
            lastCustomNonConfigurationInstance, //
            History.single(TasksKey()))

        // this method is called to handle onPause/onResume/onSaveInstanceState/onDestroy automatically
        backstackDelegate.registerForLifecycleCallbacks(this) 

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        this.fragmentStateChanger = FragmentStateChanger(supportFragmentManager, R.id.root)

        backstackDelegate.setStateChanger(this) // this is when everything is initialized
    }

    override fun onRetainCustomNonConfigurationInstance() =
        backstackDelegate.onRetainCustomNonConfigurationInstance()

Optional: All methods called

This is to demonstrate the most fine-grained behavior with BackstackDelegate.

public class MainActivity
        extends AppCompatActivity
        implements StateChanger {
    @BindView(R.id.root)
    RelativeLayout root;

    BackstackDelegate backstackDelegate;
    FragmentStateChanger fragmentStateChanger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // for fragments
        backstackDelegate = new BackstackDelegate();
        backstackDelegate.onCreate(savedInstanceState, //    <-- restoration BEFORE super.onCreate()!
                getLastCustomNonConfigurationInstance(), //
                History.single(TasksKey.create()));

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        this.fragmentStateChanger = new FragmentStateChanger(getSupportFragmentManager(), R.id.root);

        backstackDelegate.setStateChanger(this); // <-- delayed init for fragments
    }

    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        return backstackDelegate.onRetainCustomNonConfigurationInstance(); // <-- !
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        backstackDelegate.onPostResume(); // <-- !
    }

    @Override
    public void onBackPressed() {
        if(!backstackDelegate.onBackPressed()) {
            super.onBackPressed();
        }
    }

    @Override
    protected void onPause() {
        backstackDelegate.onPause(); // <-- !
        super.onPause();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        backstackDelegate.onSaveInstanceState(outState); // <-- !
    }

    @Override
    protected void onDestroy() {
        backstackDelegate.onDestroy(); // <-- !
        super.onDestroy();
    }

    @Override
    public void handleStateChange(StateChange stateChange, Callback completionCallback) {
        if(stateChange.topNewState().equals(stateChange.topPreviousState())) {
            // no-op
            completionCallback.stateChangeComplete();
            return;
        }

        fragmentStateChanger.handleStateChange(stateChange); // <--- check sample for this

        completionCallback.stateChangeComplete();
    }
}
Clone this wiki locally