From ef50094e3e7a61e62e96c02c61aefeff2e2918ef Mon Sep 17 00:00:00 2001 From: Adrian Kuta Date: Wed, 10 Jun 2026 12:52:14 +0200 Subject: [PATCH] feat(characters): Koin module + nav graph + wire into :app (REDI-89) - charactersPresentationModule: viewModelOf(::CharacterListViewModel) (in the UI-agnostic module). - @Serializable CharacterListRoute + NavGraphBuilder.charactersGraph { composable } in presentation-compose (serialization plugin added for type-safe routes). - :app registers coreDataModule + charactersDataModule + charactersPresentationModule in startKoin, and hosts a NavHost(startDestination = CharacterListRoute) calling charactersGraph. - core:data manifest declares INTERNET (merges into :app) for live API calls. --- app/build.gradle.kts | 6 ++++ .../example/architecture/ArchitectureApp.kt | 10 +++++-- .../com/example/architecture/MainActivity.kt | 29 ++++++++----------- core/data/src/main/AndroidManifest.xml | 7 +++++ .../presentation-compose/build.gradle.kts | 2 ++ .../compose/CharactersNavigation.kt | 21 ++++++++++++++ .../di/CharactersPresentationModule.kt | 10 +++++++ 7 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 core/data/src/main/AndroidManifest.xml create mode 100644 feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt create mode 100644 feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 63d4afe..fd105f7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,10 +16,16 @@ dependencies { implementation(project(":core:data")) implementation(project(":core:design-system")) + // Characters feature: data + presentation (Koin modules) + Compose renderer (nav graph). + implementation(project(":feature:characters:data")) + implementation(project(":feature:characters:presentation")) + implementation(project(":feature:characters:presentation-compose")) + implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.bundles.lifecycle.compose) + implementation(libs.androidx.navigation.compose) // Material Components — required for the Material3 XML Activity theme. implementation(libs.material) // Logging — the DebugTree is planted here; other modules log via Timber's static API. diff --git a/app/src/main/kotlin/com/example/architecture/ArchitectureApp.kt b/app/src/main/kotlin/com/example/architecture/ArchitectureApp.kt index 3005658..6842088 100644 --- a/app/src/main/kotlin/com/example/architecture/ArchitectureApp.kt +++ b/app/src/main/kotlin/com/example/architecture/ArchitectureApp.kt @@ -2,14 +2,16 @@ package com.example.architecture import android.app.Application import com.example.architecture.core.data.di.coreDataModule +import com.example.architecture.feature.characters.data.di.charactersDataModule +import com.example.architecture.feature.characters.presentation.di.charactersPresentationModule import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.context.startKoin import timber.log.Timber /** - * Single Koin entry point. Feature modules append their own `*DataModule` / `*PresentationModule` - * to the [modules] list — assembly happens only here, never inside feature modules. + * Single Koin entry point. Every feature's `*DataModule` / `*PresentationModule` is assembled here, + * never inside feature modules. */ class ArchitectureApp : Application() { override fun onCreate() { @@ -24,7 +26,11 @@ class ArchitectureApp : Application() { androidLogger() androidContext(this@ArchitectureApp) modules( + // core coreDataModule, + // characters feature + charactersDataModule, + charactersPresentationModule, ) } } diff --git a/app/src/main/kotlin/com/example/architecture/MainActivity.kt b/app/src/main/kotlin/com/example/architecture/MainActivity.kt index abbb074..bbeab40 100644 --- a/app/src/main/kotlin/com/example/architecture/MainActivity.kt +++ b/app/src/main/kotlin/com/example/architecture/MainActivity.kt @@ -4,31 +4,26 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import com.example.architecture.core.design.system.component.AppScaffold +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController import com.example.architecture.core.design.system.theme.AppTheme +import com.example.architecture.feature.characters.presentation.compose.CharacterListRoute +import com.example.architecture.feature.characters.presentation.compose.charactersGraph class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - // Compose themes via AppTheme; the navigation host lands in a later milestone. AppTheme { - AppScaffold { innerPadding -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), - contentAlignment = Alignment.Center, - ) { - Text(text = "Android Architecture Showcase") - } + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = CharacterListRoute, + ) { + charactersGraph( + onCharacterClick = { /* Detail navigation is wired in the next milestone. */ }, + ) } } } diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f15e066 --- /dev/null +++ b/core/data/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/feature/characters/presentation-compose/build.gradle.kts b/feature/characters/presentation-compose/build.gradle.kts index c9981f0..016b9c0 100644 --- a/feature/characters/presentation-compose/build.gradle.kts +++ b/feature/characters/presentation-compose/build.gradle.kts @@ -1,5 +1,7 @@ plugins { alias(libs.plugins.architecture.android.feature) + // For @Serializable type-safe navigation routes. + alias(libs.plugins.architecture.kotlinx.serialization) } android { diff --git a/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt new file mode 100644 index 0000000..3ae9849 --- /dev/null +++ b/feature/characters/presentation-compose/src/main/kotlin/com/example/architecture/feature/characters/presentation/compose/CharactersNavigation.kt @@ -0,0 +1,21 @@ +package com.example.architecture.feature.characters.presentation.compose + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import kotlinx.serialization.Serializable + +/** Type-safe route for the characters list screen. */ +@Serializable +data object CharacterListRoute + +/** + * The characters feature nav graph. `:app` only calls this and supplies cross-screen navigation as + * a callback. The detail destination is added here in a later milestone. + */ +fun NavGraphBuilder.charactersGraph( + onCharacterClick: (Int) -> Unit, +) { + composable { + CharacterListRoot(onCharacterClick = onCharacterClick) + } +} diff --git a/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt new file mode 100644 index 0000000..466b610 --- /dev/null +++ b/feature/characters/presentation/src/main/kotlin/com/example/architecture/feature/characters/presentation/di/CharactersPresentationModule.kt @@ -0,0 +1,10 @@ +package com.example.architecture.feature.characters.presentation.di + +import com.example.architecture.feature.characters.presentation.CharacterListViewModel +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +/** Presentation DI for the characters feature. Lives with the (UI-agnostic) ViewModel it provides. */ +val charactersPresentationModule = module { + viewModelOf(::CharacterListViewModel) +}