Skip to content

Back stack

Implements a simple linear history:

  • The last element at the end of the stack is considered "active".
  • All other elements are considered stashed.
  • Children associated with stashed elements are off the screen but kept alive (see how the counter values reflect this on the video)

The back stack can never be empty – it always contains at least one element.

The back stack also supports different back press and operation strategies (see further down below).

States

enum class State {
    CREATED, ACTIVE, STASHED, DESTROYED,
}

Visualisation of states

Check out the apps in our Coding challenges – they have an embedded visualisation of what happens to all the elements inside the back stack (look at the row of orange boxes below the logo).

Constructing the back stack

As the back stack can never be empty, it's required to define an initial target.

class BackStack<NavTarget : Any>(
    initialElement: NavTarget,
    savedStateMap: SavedStateMap?,
    // Optional parameters are omitted
)

Default on screen resolution

As a default, only the active element is considered on screen.

object BackStackOnScreenResolver : OnScreenStateResolver<State> {
    override fun isOnScreen(state: State): Boolean =
        when (state) {
            State.CREATED,
            State.STASHED,
            State.DESTROYED -> false
            State.ACTIVE -> true
        }
}

Default transition handlers

BackStackFader

rememberBackstackFader()

Adds simple cross-fading transitions

BackStackSlider

rememberBackstackSlider()

Adds horizontal sliding transitions so that the ACTIVE element is in the center; other states are animated from / to the left or the right edge of the screen.

Operations

Push

backStack.push(navTarget)

Effect on stack:

[A, B, C] + Push(D) = [A, B, C, D]

Transitions the active element ACTIVE -> STASHED. Adds a new element at the end of the stack with a CREATED -> ACTIVE transition.

Replace

backStack.replace(navTarget)

Effect on stack:

[A, B, C] + Replace(D) = [A, B, D]

Transitions the active element ACTIVE -> DESTROYED, which will be removed when the transition finishes. Adds a new element at the end of the stack with a CREATED -> ACTIVE transition.

Pop

backStack.pop(navTarget)

Effect on stack:

[A, B, C] + Pop = [A, B]

Transitions the active element ACTIVE -> DESTROYED, which will be removed when the transition finishes. Transitions the last stashed element STASHED -> ACTIVE.

Single top

backStack.singleTop(navTarget)

Effect on stack: depends on the contents of the stack:

[A, B, C, D] + SingleTop(B)  = [A, B]          // of same type and equals, acts as n * Pop
[A, B, C, D] + SingleTop(B') = [A, B']         // of same type but not equals, acts as n * Pop + Replace
[A, B, C, D] + SingleTop(E)  = [A, B, C, D, E] // not found, acts as Push

Back press strategy

You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own.

class BackStack<NavTarget : Any>(
    /* ... */
    backPressHandler: BackPressHandlerStrategy<NavTarget, State> = PopBackPressHandler(),
    /* ... */
) 

PopBackPressHandler

The default back press handling strategy. Runs a Pop operation.

DontHandleBackPress

Serves as a no-op.

Operation strategy

You can override the default strategy in the constructor. You're not limited to using the provided classes, feel free to implement your own.

class BackStack<NavTarget : Any>(
    /* ... */
    operationStrategy: OperationStrategy<NavTarget, State> = ExecuteImmediately(),    
    /* ... */
)

ExecuteImmediately

The default strategy. New operations are executed without any questions, regardless of any already running transitions.

FinishTransitionsOnNewOperation

All running transitions are abruptly finished when a new one is started

QueueOperations

The new operation is queued and executed after the current one finishes

IgnoreIfThereAreUnfinishedTransitions

Runs the new one only if there are no transitions happening currently; ignore and discard it otherwise