NavDestination
Marks a presenter class as a navigation destination so the codegen processor can wire it into the consumer's navigation graph at compile time.
Without this annotation a presenter is just a class. The consumer would have to manually write a Metro @GraphExtension for it, manually register a NavDestination factory, manually register a NavRouteBinding for polymorphic save and restore, and repeat that boilerplate for every screen, overlay, and tab in the app. With this annotation the processor emits all of that for you in a file that lives next to your presenter and that your IDE can navigate to.
The kind parameter picks the destination role (a regular stack screen, a modal overlay, or a top level tab anchor) and the processor emits the matching pair of files.
Example
@Inject
@NavDestination(
route = ShowsRoute::class,
parentScope = ActivityScope::class,
kind = DestinationKind.SCREEN,
)
class ShowsPresenter(
componentContext: ComponentContext,
) : ComponentContext by componentContextThe processor emits two files into the presenter's <package>.di package: a Metro graph extension named ShowsScreenGraph and a binding named ShowsNavDestinationBinding. The binding contributes the presenter to the consumer's Set<NavDestination<*>> and Set<NavRouteBinding<*>> multibindings.
Generated artifacts by kind
DestinationKind.SCREEN emits
<Presenter>ScreenGraph(a@GraphExtension(route)interface) and<Presenter>NavDestinationBindingcontributing@IntoSet NavDestination.Screenplus@IntoSet NavRouteBinding<*>.DestinationKind.OVERLAY emits the same files as DestinationKind.SCREEN except the destination is
NavDestination.Overlay. The consumer's navigator decides at runtime whether to push the destination onto the back stack or present it as an overlay, based on the destination subclass and the route's type.DestinationKind.TAB_ROOT emits
<Presenter>TabGraph(a@GraphExtension(route)interface) and<Presenter>TabDestinationBindingcontributing@IntoSet NavDestination.TabRootplus@IntoSet NavRootBinding<*>. The route is aNavRoot(typically adata object) and doubles as the graph scope.
Parameterized presenters
If the presenter uses Metro's @AssistedInject with a nested @AssistedFactory, the processor detects this and emits a parameterized binding that extracts the runtime parameter from the route. The presenter's single @Assisted constructor parameter must have the same type as a property on the route class. The processor reads the route property at navigation time and passes it through the assisted factory.
@AssistedInject
@NavDestination(
route = ShowDetailsRoute::class,
parentScope = ActivityScope::class,
kind = DestinationKind.SCREEN,
)
class ShowDetailsPresenter(
@Assisted val showId: Long,
componentContext: ComponentContext,
) : ComponentContext by componentContext {
@AssistedFactory
interface Factory {
fun create(showId: Long): ShowDetailsPresenter
}
}
@Serializable
data class ShowDetailsRoute(val showId: Long) : NavRouteValidation
The processor reports a compile error if any of the following hold:
The annotated symbol is not a class.
kind is not
SCREEN,OVERLAY, orTAB_ROOT.The presenter is parameterized but does not have exactly one
@Assistedconstructor parameter.kind is
TAB_ROOTand the presenter declares a nested@AssistedFactory. Tab roots must use plain@Injectbecause their route is a singletondata objectand carries no runtime payload.
Properties
The destination role. See DestinationKind.
The parent dependency injection scope hosting the generated binding. Typically ActivityScope::class in the consumer project.
The feature's route class. For DestinationKind.SCREEN or DestinationKind.OVERLAY it implements NavRoute. For DestinationKind.TAB_ROOT it implements NavRoot. The route class doubles as the generated graph's scope marker.