REDI-93: host the Views list in the Compose NavHost (Compose<->View interop)

:app now owns the cross-toolkit interop. MainActivity becomes a FragmentActivity and
hosts CharacterListFragment inside the Compose NavHost via AndroidFragment at a new
@Serializable CharactersViewsRoute (defined in :app, since :app owns interop — the Views
module stays nav-agnostic). The overflow menu's 'Open as Views' / 'About' entries and the
Views item-click -> detail navigation are all injected as callbacks, keeping the renderers
decoupled. Assemble aboutPresentationModule in Koin; add androidx.fragment:fragment-compose.
The Material3 Activity theme styles both toolkits.
This commit is contained in:
2026-06-10 13:45:00 +02:00
parent e230aa77d8
commit 6577a85a15
4 changed files with 56 additions and 4 deletions

View File

@@ -2,6 +2,8 @@ plugins {
alias(libs.plugins.architecture.android.application) alias(libs.plugins.architecture.android.application)
alias(libs.plugins.architecture.compose) alias(libs.plugins.architecture.compose)
alias(libs.plugins.architecture.koin) alias(libs.plugins.architecture.koin)
// For the @Serializable CharactersViewsRoute (Compose↔View interop destination).
alias(libs.plugins.architecture.kotlinx.serialization)
} }
android { android {
@@ -16,16 +18,23 @@ 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). // Characters feature: data + presentation (Koin modules) + both renderers (Compose nav graph,
// Views Fragment hosted via interop).
implementation(project(":feature:characters:data")) implementation(project(":feature:characters:data"))
implementation(project(":feature:characters:presentation")) implementation(project(":feature:characters:presentation"))
implementation(project(":feature:characters:presentation-compose")) implementation(project(":feature:characters:presentation-compose"))
implementation(project(":feature:characters:presentation-views"))
// About feature (MVVM contrast).
implementation(project(":feature:about:presentation"))
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) implementation(libs.androidx.navigation.compose)
// Compose↔View interop: hosts a Fragment inside the Compose NavHost.
implementation(libs.androidx.fragment.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,6 +2,7 @@ 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.about.presentation.di.aboutPresentationModule
import com.example.architecture.feature.characters.data.di.charactersDataModule import com.example.architecture.feature.characters.data.di.charactersDataModule
import com.example.architecture.feature.characters.presentation.di.charactersPresentationModule import com.example.architecture.feature.characters.presentation.di.charactersPresentationModule
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
@@ -31,6 +32,8 @@ class ArchitectureApp : Application() {
// characters feature // characters feature
charactersDataModule, charactersDataModule,
charactersPresentationModule, charactersPresentationModule,
// about feature (MVVM contrast)
aboutPresentationModule,
) )
} }
} }

View File

@@ -0,0 +1,11 @@
package com.example.architecture
import kotlinx.serialization.Serializable
/**
* Route for the characters list rendered with the classic **Views** toolkit. It lives in `:app`
* because `:app` owns Compose↔View interop — the `:feature:characters:presentation-views` module
* stays navigation-agnostic (it knows nothing about Compose Navigation or this route).
*/
@Serializable
data object CharactersViewsRoute

View File

@@ -1,16 +1,30 @@
package com.example.architecture package com.example.architecture
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.fragment.app.FragmentActivity
import androidx.fragment.compose.AndroidFragment
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.architecture.core.design.system.theme.AppTheme import com.example.architecture.core.design.system.theme.AppTheme
import com.example.architecture.feature.about.presentation.AboutRoute
import com.example.architecture.feature.about.presentation.aboutGraph
import com.example.architecture.feature.characters.presentation.compose.CharacterDetailRoute
import com.example.architecture.feature.characters.presentation.compose.CharacterListRoute import com.example.architecture.feature.characters.presentation.compose.CharacterListRoute
import com.example.architecture.feature.characters.presentation.compose.charactersGraph import com.example.architecture.feature.characters.presentation.compose.charactersGraph
import com.example.architecture.feature.characters.presentation.views.CharacterListFragment
class MainActivity : ComponentActivity() { /**
* Hosts the single Compose NavHost and owns every cross-feature / cross-toolkit wiring:
* - the characters graph (Compose list + detail),
* - the About graph (MVVM contrast),
* - the Views renderer embedded via [AndroidFragment] (Compose↔View interop).
*
* Extends [FragmentActivity] (not plain ComponentActivity) so [AndroidFragment] has a FragmentManager.
*/
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
@@ -22,8 +36,23 @@ class MainActivity : ComponentActivity() {
startDestination = CharacterListRoute, startDestination = CharacterListRoute,
) { ) {
charactersGraph( charactersGraph(
onCharacterClick = { /* Detail navigation is wired in the next milestone. */ }, navController = navController,
onOpenAbout = { navController.navigate(AboutRoute) },
onOpenViewsList = { navController.navigate(CharactersViewsRoute) },
) )
aboutGraph(
onNavigateBack = { navController.popBackStack() },
)
// Compose↔View interop: the same characters list, rendered by a Fragment. :app
// injects the navigation callbacks so the Views module stays nav-agnostic.
composable<CharactersViewsRoute> {
AndroidFragment<CharacterListFragment> { fragment ->
fragment.onCharacterClick = { id ->
navController.navigate(CharacterDetailRoute(id))
}
fragment.onNavigateBack = { navController.popBackStack() }
}
}
} }
} }
} }