name: navigation-architecture description: 'AniTrend UI navigation architecture guide. Use when tracing deep links, router/provider contracts, Activity-to-Fragment flows, Compose NavHost usage, or adding a new screen destination.' argument-hint: 'Describe the flow you want to trace or the destination you want to add'
Skill: Navigation Architecture
Overview
AniTrend navigation is hybrid. Do not assume a single app-wide Compose NavHost, and do not
assume features navigate to each other by importing activities directly.
The current model has three layers:
- Deep link entry in
:android:deeplinkmaps external URIs to internal intents. - Shared router contracts in
:app:navigationexpose stable navigation entry points and parameter types. - Feature-owned destinations implement those contracts through Koin-backed
FeatureProviderclasses, then choose whether the feature UI is activity-only, fragment-backed, or local Compose navigation.
This matters because the repo is still in a staged migration. The app is not moving to a full
Compose-only navigation stack yet. Respect the existing Activity -> Fragment boundaries and
only use local Compose navigation where the feature already does so.
For concrete deep-link, drawer, app-shell, and review route anchors, use the
layer example matrix. If the work may
reuse an existing Android-side helper or shell API, pair this skill with
android-platform-patterns.
When to Use
- Trace how a URI or button click reaches a screen.
- Add a new screen or route without breaking module boundaries.
- Debug why a router resolves to the wrong activity, fragment, or sheet.
- Understand whether a feature is legacy fragment-backed or Compose-first.
- Review navigation changes for architecture drift.
Mental Model
1. External deep links
External URIs enter through DeepLinkScreen, not straight into feature screens.
Flow:
android/deeplink/src/main/kotlin/co/anitrend/android/deeplink/component/screen/DeepLinkScreen.ktreceives the incoming intent.DeepLinkViewModelcallsDeepLinkRouter.forMatchingIntent(uri).DeepLinkRouter.Provideris implemented byandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/provider/FeatureProvider.kt.- That provider delegates to a
DeepLinkParserbuilt inandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/koin/Modules.kt. - Route objects in
AppRoutes.ktandWebRoutes.ktconvert the URI into a target router call such asSettingsRouter.forActivity(...)orMainRouter.forActivity(...). - The deep-link screen launches the resolved intent, then exits.
2. Internal router-driven navigation
Internal navigation goes through router singletons in
app/navigation/src/main/kotlin/co/anitrend/navigation/NavigationTargets.kt.
Each router:
- extends
NavigationRouter - resolves its provider through Koin
- exposes a contract such as
activity(),fragment(), orsheet() - defines typed params with
IParamwhen payload is required
The shared helpers in
app/navigation/src/main/kotlin/co/anitrend/navigation/extensions/RouterExtensions.kt build or
start intents from those providers. Callers should prefer router helpers over constructing intents
directly.
3. Feature-owned UI host style
Once a router resolves a feature, the target feature usually matches one of these patterns:
- Activity only:
FeatureProvider.activity()returns a screen class and that screen renders the whole feature directly. - Activity -> Fragment bridge: the screen is Compose-hosted but embeds a fragment through
FragmentItemHost, which commits the fragment into aFragmentContainerView. - Activity -> Compose -> local NavHost: the feature uses
rememberNavController()and a feature-localNavHostfor section-to-section movement inside the screen.
Examples:
app/src/main/kotlin/co/anitrend/component/screen/MainScreen.ktis the app shell and still uses fragment transactions plus the navigation drawer router.feature/airing/src/main/kotlin/co/anitrend/airing/component/screen/AiringScreen.ktshows the bridge pattern: activity host, Compose scaffold, thenFragmentItemHost(fragment = AiringRouter.forFragment()).feature/settings/src/main/kotlin/co/anitrend/settings/component/screen/SettingsScreen.ktandfeature/settings/src/main/kotlin/co/anitrend/settings/component/compose/SettingsCompose.ktshow the Compose-local navigation pattern withSettingsRouter.Destinationand a local ComposeNavHost.
Procedure: Trace an Existing Flow
- Identify the entry type.
- External URI: start in
:android:deeplink. - Internal button/menu action: start from the router call site.
- Screen-local tab/section change: start inside the feature's Compose or fragment host.
- External URI: start in
- Find the router object in
NavigationTargets.kt.- Check its provider interface for required capabilities like
activity(),fragment(), orsheet(). - Check whether it defines a typed param class.
- Check its provider interface for required capabilities like
- Find the feature implementation.
- Open the feature's
provider/FeatureProvider.kt. - Open the feature's
koin/Modules.ktand confirmfactory<XxxRouter.Provider> { FeatureProvider() }.
- Open the feature's
- Identify the host model.
setContentplusFragmentItemHostmeans the feature is still fragment-backed.setContentplusrememberNavControllerandNavHostmeans navigation is local to that feature.- XML view binding plus fragment transactions usually means app-shell or legacy flow.
- Follow payload transport.
- Routers use
NavPayload,IParam, and intent extras. - Deep-link routes often build params first, then call
forActivity(...).
- Routers use
- Confirm the final destination class.
- Activity class comes from
activity(). - Fragment class comes from
fragment(). - Bottom sheets or dialogs come from
sheet().
- Activity class comes from
Procedure: Add or Change Navigation
- Decide which layer owns the change.
- External URI support: update
:android:deeplinkroutes. - Cross-feature navigation: update or add a router in
:app:navigation. - Section switching within one feature screen: keep it local to that feature.
- External URI support: update
- Define or extend the router contract in
NavigationTargets.kt.- Add a new router or destination enum if needed.
- Add an
IParampayload type when arguments must cross module boundaries.
- Implement the feature provider.
- Add or update
provider/FeatureProvider.ktin the owning feature module. - Return only the classes owned by that feature.
- Add or update
- Bind the provider in the feature's
koin/Modules.kt.- Use
factory<XxxRouter.Provider> { FeatureProvider() }.
- Use
- Add the host implementation.
- Fragment-backed feature: ensure the fragment is registered in Koin and returned by
fragment(). - Compose-local feature: update the feature screen and local
NavHostroutes. - Sheet/dialog feature: return the dialog fragment class from
sheet().
- Fragment-backed feature: ensure the fragment is registered in Koin and returned by
- If the route must be deep-linkable, add a
Routeimplementation and register it inandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/koin/Modules.kt.
Apply the Decision Rules first to scope the change, then follow the Procedure steps within that scope.
Decision Rules
- If the user comes from a browser or app link, go through
DeepLinkScreenand route objects. - If one feature opens another feature, use the shared router contract, not direct activity imports.
- If the feature already hosts a fragment via
FragmentItemHost, keep that boundary unless the task explicitly migrates the feature. - If the change is only inside one Compose screen, use the feature-local
NavHostonly when that feature already owns a ComposeNavHost. When creating a brand-new feature, default to the fragment-backed or activity-only pattern unless the task explicitly says the feature is Compose-first. - Do not replace current router or fragment patterns with Navigation 3 assumptions. This repo is still on the existing AndroidX Navigation Compose setup where Compose is incremental, not the global navigation authority.
Review Checklist
- Router contract lives in
:app:navigation, not in the feature module. - Feature provider is bound through Koin and satisfies every router method the app calls.
- Navigation arguments cross module boundaries as typed params, not ad-hoc string extras.
- Deep-linkable destinations are added to both a
Routeimplementation and the parser builder. - Feature code does not import unrelated feature activities directly when a router exists.
- Compose navigation changes stay local unless the task explicitly changes shared navigation.
Canonical Files
For a curated cross-layer list, including Android shell and deep-link anchors, use the layer example matrix.
app/navigation/src/main/kotlin/co/anitrend/navigation/NavigationTargets.ktapp/navigation/src/main/kotlin/co/anitrend/navigation/router/NavigationRouter.ktapp/navigation/src/main/kotlin/co/anitrend/navigation/provider/INavigationProvider.ktapp/navigation/src/main/kotlin/co/anitrend/navigation/extensions/RouterExtensions.ktandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/component/screen/DeepLinkScreen.ktandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/component/viewmodel/DeepLinkViewModel.ktandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/koin/Modules.ktandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/component/route/AppRoutes.ktandroid/deeplink/src/main/kotlin/co/anitrend/android/deeplink/component/route/WebRoutes.ktapp/src/main/kotlin/co/anitrend/component/screen/MainScreen.ktcommon/shared/src/main/kotlin/co/anitrend/common/shared/ui/compose/SharedCompose.ktfeature/airing/src/main/kotlin/co/anitrend/airing/component/screen/AiringScreen.ktfeature/airing/src/main/kotlin/co/anitrend/airing/provider/FeatureProvider.ktfeature/settings/src/main/kotlin/co/anitrend/settings/component/screen/SettingsScreen.ktfeature/settings/src/main/kotlin/co/anitrend/settings/component/compose/SettingsCompose.kt
Useful External Reference
- Koin Context7 library ID:
/insertkoinio/koinUse this when you need framework-level DI syntax or lifecycle details beyond the repo's local patterns.