Files
coco 723ce1af5c a
2026-07-03 15:12:48 +08:00

168 lines
4.8 KiB
Markdown

# Implementing your own navigation models
A step-by-step guide. You can also take a look at other existing examples to see these in practice.
## Step 1
Create the class; define your possible states; define your initial state.
```kotlin
class Foo<NavTarget : Any>(
initialItems: List<NavTarget> = listOf(),
savedStateMap: SavedStateMap?
) : BaseNavModel<NavTarget, Foo.State>(
screenResolver = FooOnScreenResolver, // We'll see about this shortly
finalState = DESTROYED, // Anything transitioning towards this state will be discarded eventually
savedStateMap = savedStateMap // It's nullable if you don't need state restoration
) {
// Your possible states for any single navigation target
enum class State {
CREATED, FOO, BAR, BAZ, DESTROYED;
}
// You can go about it any other way.
// Back stack for example defines only a single element.
// Here we take all the <NavTarget> elements and make them transition CREATED -> FOO immediately.
override val initialElements = initialItems.map {
FooElement(
key = NavKey(it),
fromState = State.CREATED,
targetState = State.FOO,
operation = Operation.Noop()
)
}
}
```
## (optional) Step 2
Add some convenience aliases:
```kotlin
typealias FooElement<NavTarget> = NavElement<NavTarget, Foo.State>
typealias FooElements<NavTarget> = NavElements<NavTarget, Foo.State>
sealed interface FooOperation<NavTarget> : Operation<NavTarget, Foo.State>
```
## Step 3
Define one or more operations.
```kotlin
@Parcelize
class SomeOperation<NavTarget : Any> : FooOperation<NavTarget> {
override fun isApplicable(elements: FooElements<NavTarget>): Boolean =
TODO("Define whether this operation is applicable given the current state")
override fun invoke(
elements: FooElements<NavTarget>,
): NavElements<NavTarget, Foo.State> =
// TODO: Mutate elements however you please. Add, remove, change.
// In this example we're changing all elements to transition to BAR.
// You can also use helper methods elements.transitionTo & elements.transitionToIndexed
elements.map {
it.transitionTo(
newTargetState = BAR,
operation = this
)
}
}
// You can add an extension method for a leaner API
fun <NavTarget : Any> Foo<NavTarget>.someOperation() {
accept(FooOperation())
}
```
## Step 4
Add the screen resolver to define which states should be / should not be part of the composition in the end:
```kotlin
object FooOnScreenResolver : OnScreenStateResolver<State> {
override fun isOnScreen(state: State): Boolean =
when (state) {
Foo.State.CREATED,
Foo.State.DESTROYED -> false
Foo.State.FOO,
Foo.State.BAR,
Foo.State.BAZ, -> true
}
}
```
## Step 5
Add one or more transition handlers to interpret different states and translate them to Jetpack Compose `Modifiers`.
```kotlin
class FooTransitionHandler<NavTarget>(
private val transitionSpec: TransitionSpec<Foo.State, Float> = { spring() }
) : ModifierTransitionHandler<NavTarget, Foo.State>() {
// TODO define a Modifier depending on the state.
// Here we'll just mutate scaling:
override fun createModifier(
modifier: Modifier,
transition: Transition<Foo.State>,
descriptor: TransitionDescriptor<NavTarget, Foo.State>
): Modifier = modifier.composed {
val scale = transition.animateFloat(
transitionSpec = transitionSpec,
targetValueByState = {
when (it) {
Foo.State.CREATED -> 0f
Foo.State.FOO -> 0.33f
Foo.State.BAR -> 0.66f
Foo.State.BAZ -> 1.0f
Foo.State.DESTROYED -> 0f
}
})
scale(scale.value)
}
}
// TODO remember to add:
@Composable
fun <NavTarget> rememberFooTransitionHandler(
transitionSpec: TransitionSpec<Foo.State, Float> = { spring() }
): ModifierTransitionHandler<NavTarget, Foo.State> = remember {
FooTransitionHandler(transitionSpec)
}
```
## Test it
Add `Children` to your `Node`. Pass your NavModel and the transition handler:
```kotlin
@Composable
override fun View(modifier: Modifier) {
Children(
modifier = Modifier.fillMaxSize(),
navModel = foo,
transitionHandler = rememberFooTransitionHandler()
)
}
```
Somewhere else in your business logic trigger the operations you defined. Make sure they're called on the same `foo` instance that you pass to the `Children` composable:
```kotlin
foo.someOperation()
```
As soon as this is triggered, elements should transition to the `BAR` state in this example, and you should see them scale up defined by the transition handler.
## Created something cool?
Let us know!