Scene decorators let you modify the scene calculated by your app's scene
strategy. In effect, they are used for a second phase of constructing the
content that's displayed by a NavDisplay.
This approach lets you encapsulate specific functionality, such as displaying common UI components, into individual scene decorators.
For example, consider a productivity app that has three top-level routes: an email inbox, a direct message inbox, and a calendar view. Such an app could use two scene decorators, one for adding a top app bar that displays information and controls for the current top-level route and another for adding a persistent navigation bar or rail to navigate between the routes.
Create a scene decorator strategy
Scene decorators follow a similar pattern as scene strategies. To define a scene
decorator, implement the SceneDecoratorStrategy interface. This interface
has a method, decorateScene, which is analogous to the
calculateScene method of the SceneStrategy interface.
decorateScene determines whether it can decorate the scene:
- If your scene decorator strategy shouldn't decorate the input scene, it returns the input scene as-is.
- If it should decorate the input scene, it returns a new
Scene. In general, the returned scene takes the input scene as a parameter and calls the input scene'scontentmethod within its owncontentmethod.
To determine if and how the input scene should be decorated, your scene
decorator strategy can consider the metadata of both the input Scene and
the entries contained within that scene.
class MySceneDecoratorStrategy<T : Any> : SceneDecoratorStrategy<T> { override fun SceneDecoratorStrategyScope<T>.decorateScene(scene: Scene<T>): Scene<T> { // `shouldDecorate` determines if the scene should be decorated based on scene.metadata, // scene.entries.metadata, or any other relevant state. return if (shouldDecorate(scene)) { MyDecoratingScene(scene) } else { scene } } } class MyDecoratingScene<T : Any>(scene: Scene<T>) : Scene<T> { // ... override val content = @Composable { scene.content() } }
Use scene decorator strategies
To use scene decorator strategies, supply them to your NavDisplay using the
sceneDecoratorStrategies parameter. When decorating scenes, NavDisplay calls
the decorateScene method of each strategy in succession, passing the output of
each call as the input to the next.
NavDisplay( // ... sceneDecoratorStrategies = listOf(firstSceneDecoratorStrategy, secondSceneDecoratorStrategy) )
Common patterns for scene decorators
When implementing scene decorators, the following are some common patterns to be aware of:
Copy properties
In many cases, the scene returned by decorating a scene should contain the same entries and have the same previous entries as the scene it's decorating. Additionally, it should likely inherit (or modify) the metadata of the scene it's decorating, rather than use the default behavior. The following code demonstrates an example of how to do this:
class CopyingScene<T : Any>(scene: Scene<T>) : Scene<T> { override val entries = scene.entries override val previousEntries = scene.previousEntries override val metadata = scene.metadata // ... }
Maintain animations
As detailed in Animate between destinations, NavDisplay automatically
animates transitions between scenes when a key derived from the class of the
current scene and its key property changes.
When introducing scene decorators to your app, the class of the scene returned
after scene decoration can remain the same, even when the class of the scene
returned during scene calculation changes. When this happens and
decorating scenes directly copy the key of the scene they're decorating, the
built-in animations no longer happen because the derived key doesn't change.
To maintain built-in animation support, decorating scenes should use a key
derived from the class and key of the scene returned by calculateScene.
class DerivedKeyScene<T : Any>(scene: Scene<T>) : Scene<T> { override val key = scene::class to scene.key // ... }