feat(characters): Koin module + nav graph + wire into :app (REDI-89)

- charactersPresentationModule: viewModelOf(::CharacterListViewModel) (in the UI-agnostic module).
- @Serializable CharacterListRoute + NavGraphBuilder.charactersGraph { composable<CharacterListRoute> }
  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.
This commit is contained in:
2026-06-10 12:52:14 +02:00
parent dd4576409d
commit ef50094e3e
7 changed files with 66 additions and 19 deletions

View File

@@ -16,10 +16,16 @@ dependencies {
implementation(project(":core:data")) implementation(project(":core:data"))
implementation(project(":core:design-system")) 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.core.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.bundles.lifecycle.compose) implementation(libs.bundles.lifecycle.compose)
implementation(libs.androidx.navigation.compose)
// Material Components — required for the Material3 XML Activity theme. // Material Components — required for the Material3 XML Activity theme.
implementation(libs.material) implementation(libs.material)
// Logging — the DebugTree is planted here; other modules log via Timber's static API. // Logging — the DebugTree is planted here; other modules log via Timber's static API.

View File

@@ -2,14 +2,16 @@ package com.example.architecture
import android.app.Application import android.app.Application
import com.example.architecture.core.data.di.coreDataModule 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.androidContext
import org.koin.android.ext.koin.androidLogger import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import timber.log.Timber import timber.log.Timber
/** /**
* Single Koin entry point. Feature modules append their own `*DataModule` / `*PresentationModule` * Single Koin entry point. Every feature's `*DataModule` / `*PresentationModule` is assembled here,
* to the [modules] list — assembly happens only here, never inside feature modules. * never inside feature modules.
*/ */
class ArchitectureApp : Application() { class ArchitectureApp : Application() {
override fun onCreate() { override fun onCreate() {
@@ -24,7 +26,11 @@ class ArchitectureApp : Application() {
androidLogger() androidLogger()
androidContext(this@ArchitectureApp) androidContext(this@ArchitectureApp)
modules( modules(
// core
coreDataModule, coreDataModule,
// characters feature
charactersDataModule,
charactersPresentationModule,
) )
} }
} }

View File

@@ -4,31 +4,26 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box import androidx.navigation.compose.NavHost
import androidx.compose.foundation.layout.fillMaxSize import androidx.navigation.compose.rememberNavController
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 com.example.architecture.core.design.system.theme.AppTheme 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() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
// Compose themes via AppTheme; the navigation host lands in a later milestone.
AppTheme { AppTheme {
AppScaffold { innerPadding -> val navController = rememberNavController()
Box( NavHost(
modifier = Modifier navController = navController,
.fillMaxSize() startDestination = CharacterListRoute,
.padding(innerPadding),
contentAlignment = Alignment.Center,
) { ) {
Text(text = "Android Architecture Showcase") charactersGraph(
} onCharacterClick = { /* Detail navigation is wired in the next milestone. */ },
)
} }
} }
} }

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Networking lives in this module, so the permission is declared here and merges into :app. -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -1,5 +1,7 @@
plugins { plugins {
alias(libs.plugins.architecture.android.feature) alias(libs.plugins.architecture.android.feature)
// For @Serializable type-safe navigation routes.
alias(libs.plugins.architecture.kotlinx.serialization)
} }
android { android {

View File

@@ -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<CharacterListRoute> {
CharacterListRoot(onCharacterClick = onCharacterClick)
}
}

View File

@@ -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)
}