# Android Clean Architecture -- Reference Patterns

Full code for the patterns summarized in `SKILL.md`. Load the section you need
when implementing that specific structure.

## Contents

- [Feature api/impl Split](#feature-apiimpl-split)
- [Cross-Module Navigation Graph Aggregation](#cross-module-navigation-graph-aggregation)

## Feature api/impl Split

Split every feature into an `:api` module (the cross-feature contract) and an
`:impl` module (everything private to the feature).

```
:feature:home:api             # Serializable routes + entry-param models only
:feature:home:impl            # Screens, ViewModels, UI state, DI
:feature:search:api
:feature:search:impl
:feature:detail:api
:feature:detail:impl
```

### The api module holds only the contract

```kotlin
// :feature:detail:api
@Serializable
data class DetailRoute(val id: String)

@Serializable
data class DetailEntryParams(val id: String, val source: String)
```

No screens, no ViewModels, no Compose dependency -- just the route and the
models a caller needs to launch the feature.

### The impl module holds everything private

```kotlin
// :feature:detail:impl -- not visible to any other feature
@HiltViewModel
class DetailViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    private val getDetailUseCase: GetDetailUseCase,
) : ViewModel() {
    private val route = savedStateHandle.toRoute<DetailRoute>()
    // ... state, intents, events
}

@Composable
internal fun DetailScreen(viewModel: DetailViewModel = hiltViewModel()) { /* ... */ }
```

### Dependency direction

```kotlin
// feature:home:impl/build.gradle.kts
dependencies {
    implementation(project(":feature:home:api"))
    implementation(project(":feature:detail:api"))   // route + entry params only
    implementation(project(":core:model"))
    implementation(project(":core:ui"))
    // NEVER implementation(project(":feature:detail:impl"))
}
```

Because no `impl` module can see another `impl`, the graph of feature modules is
acyclic by construction, the modules compile in parallel, and a caller can only
couple to a feature through its published route and entry params.

## Cross-Module Navigation Graph Aggregation

Each `:impl` module contributes its destinations to a shared `Set<NavGraph>` via
Hilt multibinding. The host module (`:app`) injects the full set and registers
every contribution into one `NavHost`, without depending on any `:impl`.

### The contribution contract

```kotlin
// :core:ui (or a :core:navigation module)
fun interface NavGraph {
    fun NavGraphBuilder.build(navController: NavHostController)
}
```

### Each feature contributes its graph

```kotlin
// :feature:home:impl
class HomeNavGraph @Inject constructor() : NavGraph {
    override fun NavGraphBuilder.build(navController: NavHostController) {
        composable<HomeRoute> {
            HomeScreen()
        }
    }
}

@Module
@InstallIn(SingletonComponent::class)
abstract class HomeNavModule {
    @Binds
    @IntoSet
    abstract fun bindHomeNavGraph(impl: HomeNavGraph): NavGraph
}
```

### The host registers the whole set

```kotlin
// :app
@Composable
fun AppNavHost(
    navGraphs: Set<@JvmSuppressWildcards NavGraph>,
    navController: NavHostController = rememberNavController(),
    startDestination: Any = HomeRoute,
) {
    NavHost(navController = navController, startDestination = startDestination) {
        navGraphs.forEach { graph -> with(graph) { build(navController) } }
    }
}
```

Injecting the set into the composable is done with a Hilt entry point or by
passing it from an `@AndroidEntryPoint` activity:

```kotlin
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    @Inject lateinit var navGraphs: Set<@JvmSuppressWildcards NavGraph>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { AppNavHost(navGraphs = navGraphs) }
    }
}
```

Adding a feature to the app is now: add its `:impl` to the `:app` runtime
classpath and its `@IntoSet` binding registers automatically. The `:app` module
never names the feature in code.
