From 9cadf2730137ea6e034e14804f5413d7412d58aa Mon Sep 17 00:00:00 2001 From: PJ Date: Sun, 26 Apr 2026 16:05:43 +0700 Subject: [PATCH] refactor(folio): replace AppComponent with Metro AppGraph in App.kt App now takes a suspend graph builder; the platform constructs LedgerDatabase off the suspend DriverFactory.create() before invoking the Metro graph factory. Routes resolve VMs through LocalAppGraph instead of the hand-rolled LocalAppComponent. Drops the loading-state placeholder comment (the empty Box is enough). --- .../src/commonMain/kotlin/app/folio/App.kt | 43 +++++++------------ .../kotlin/app/folio/AppComponent.kt | 14 ------ .../kotlin/app/folio/di/LocalAppGraph.kt | 7 +++ 3 files changed, 22 insertions(+), 42 deletions(-) delete mode 100644 examples/folio/app/shared/src/commonMain/kotlin/app/folio/AppComponent.kt create mode 100644 examples/folio/app/shared/src/commonMain/kotlin/app/folio/di/LocalAppGraph.kt diff --git a/examples/folio/app/shared/src/commonMain/kotlin/app/folio/App.kt b/examples/folio/app/shared/src/commonMain/kotlin/app/folio/App.kt index 5927ccc..a83143b 100644 --- a/examples/folio/app/shared/src/commonMain/kotlin/app/folio/App.kt +++ b/examples/folio/app/shared/src/commonMain/kotlin/app/folio/App.kt @@ -16,36 +16,25 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId -import app.folio.core.data.DriverFactory -import app.folio.core.data.Repository -import app.folio.core.data.SqlLedgerStore -import app.folio.db.LedgerDatabase +import app.folio.di.AppGraph +import app.folio.di.LocalAppGraph import app.folio.feature.account.AddAccountRoute import app.folio.feature.auth.LoginRoute import app.folio.feature.home.HomeRoute import app.folio.feature.ledger.AddTransactionRoute import app.folio.feature.ledger.LedgerRoute -import app.folio.navigation.Navigator import app.folio.navigation.Route +import app.folio.ui.testTagsAsResourceId import app.folio.ui.theme.LedgerTheme import app.folio.ui.theme.LocalTokens import app.folio.ui.theme.Tokens -@OptIn(ExperimentalComposeUiApi::class) @Composable -fun App(driverFactory: DriverFactory) { - var component by remember { mutableStateOf(null) } +fun App(graphBuilder: suspend () -> AppGraph) { + var graph by remember { mutableStateOf(null) } - LaunchedEffect(Unit) { - val driver = driverFactory.create() - val db = LedgerDatabase(driver) - val store = SqlLedgerStore(db) - component = AppComponent(repository = Repository(store), navigator = Navigator(initial = Route.Home)) - } + LaunchedEffect(Unit) { graph = graphBuilder() } LedgerTheme { val t = Tokens() @@ -55,14 +44,12 @@ fun App(driverFactory: DriverFactory) { .fillMaxSize() .background(t.bg) .windowInsetsPadding(WindowInsets.safeDrawing) - .semantics { testTagsAsResourceId = true }, + .testTagsAsResourceId(), contentAlignment = Alignment.Center, ) { - val c = component - if (c == null) { - // Loading: keep blank to avoid surprising the spec runner. - } else { - CompositionLocalProvider(LocalAppComponent provides c) { + val g = graph + if (g != null) { + CompositionLocalProvider(LocalAppGraph provides g) { AppContent() } } @@ -73,15 +60,15 @@ fun App(driverFactory: DriverFactory) { @Composable private fun AppContent() { - val component = LocalAppComponent.current - val session by component.repository.session.collectAsState() - val route by component.navigator.current.collectAsState() + val graph = LocalAppGraph.current + val session by graph.repository.session.collectAsState() + val route by graph.navigator.current.collectAsState() LaunchedEffect(session, route) { if (session == null && route !is Route.Login) { - component.navigator.replace(Route.Login) + graph.navigator.replace(Route.Login) } else if (session != null && route is Route.Login) { - component.navigator.replace(Route.Home) + graph.navigator.replace(Route.Home) } } diff --git a/examples/folio/app/shared/src/commonMain/kotlin/app/folio/AppComponent.kt b/examples/folio/app/shared/src/commonMain/kotlin/app/folio/AppComponent.kt deleted file mode 100644 index a07d8b9..0000000 --- a/examples/folio/app/shared/src/commonMain/kotlin/app/folio/AppComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.folio - -import androidx.compose.runtime.staticCompositionLocalOf -import app.folio.core.data.Repository -import app.folio.navigation.Navigator - -class AppComponent( - val repository: Repository, - val navigator: Navigator, -) - -val LocalAppComponent = staticCompositionLocalOf { - error("AppComponent not provided. Wrap content in CompositionLocalProvider(LocalAppComponent provides ...).") -} diff --git a/examples/folio/app/shared/src/commonMain/kotlin/app/folio/di/LocalAppGraph.kt b/examples/folio/app/shared/src/commonMain/kotlin/app/folio/di/LocalAppGraph.kt new file mode 100644 index 0000000..a40236e --- /dev/null +++ b/examples/folio/app/shared/src/commonMain/kotlin/app/folio/di/LocalAppGraph.kt @@ -0,0 +1,7 @@ +package app.folio.di + +import androidx.compose.runtime.staticCompositionLocalOf + +val LocalAppGraph = staticCompositionLocalOf { + error("AppGraph not provided. Wrap content in CompositionLocalProvider(LocalAppGraph provides ...).") +}