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).
This commit is contained in:
pj committed 2026-04-26 16:05:43 +07:00
1 parent 2fb208bda9
commit 9cadf27301
3 files changed
+22 -42

No files matched your search

@@ -16,36 +16,25 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.semantics import app.folio.di.AppGraph
import androidx.compose.ui.semantics.testTagsAsResourceId import app.folio.di.LocalAppGraph
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.feature.account.AddAccountRoute import app.folio.feature.account.AddAccountRoute
import app.folio.feature.auth.LoginRoute import app.folio.feature.auth.LoginRoute
import app.folio.feature.home.HomeRoute import app.folio.feature.home.HomeRoute
import app.folio.feature.ledger.AddTransactionRoute import app.folio.feature.ledger.AddTransactionRoute
import app.folio.feature.ledger.LedgerRoute import app.folio.feature.ledger.LedgerRoute
import app.folio.navigation.Navigator
import app.folio.navigation.Route import app.folio.navigation.Route
import app.folio.ui.testTagsAsResourceId
import app.folio.ui.theme.LedgerTheme import app.folio.ui.theme.LedgerTheme
import app.folio.ui.theme.LocalTokens import app.folio.ui.theme.LocalTokens
import app.folio.ui.theme.Tokens import app.folio.ui.theme.Tokens
@OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
fun App(driverFactory: DriverFactory) { fun App(graphBuilder: suspend () -> AppGraph) {
var component by remember { mutableStateOf<AppComponent?>(null) } var graph by remember { mutableStateOf<AppGraph?>(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) { graph = graphBuilder() }
val driver = driverFactory.create()
val db = LedgerDatabase(driver)
val store = SqlLedgerStore(db)
component = AppComponent(repository = Repository(store), navigator = Navigator(initial = Route.Home))
}
LedgerTheme { LedgerTheme {
val t = Tokens() val t = Tokens()
@@ -55,14 +44,12 @@ fun App(driverFactory: DriverFactory) {
.fillMaxSize() .fillMaxSize()
.background(t.bg) .background(t.bg)
.windowInsetsPadding(WindowInsets.safeDrawing) .windowInsetsPadding(WindowInsets.safeDrawing)
.semantics { testTagsAsResourceId = true }, .testTagsAsResourceId(),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
val c = component val g = graph
if (c == null) { if (g != null) {
// Loading: keep blank to avoid surprising the spec runner. CompositionLocalProvider(LocalAppGraph provides g) {
} else {
CompositionLocalProvider(LocalAppComponent provides c) {
AppContent() AppContent()
} }
} }
@@ -73,15 +60,15 @@ fun App(driverFactory: DriverFactory) {
@Composable @Composable
private fun AppContent() { private fun AppContent() {
val component = LocalAppComponent.current val graph = LocalAppGraph.current
val session by component.repository.session.collectAsState() val session by graph.repository.session.collectAsState()
val route by component.navigator.current.collectAsState() val route by graph.navigator.current.collectAsState()
LaunchedEffect(session, route) { LaunchedEffect(session, route) {
if (session == null && route !is Route.Login) { 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) { } else if (session != null && route is Route.Login) {
component.navigator.replace(Route.Home) graph.navigator.replace(Route.Home)
} }
} }
@@ -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<AppComponent> {
error("AppComponent not provided. Wrap content in CompositionLocalProvider(LocalAppComponent provides ...).")
}
@@ -0,0 +1,7 @@
package app.folio.di
import androidx.compose.runtime.staticCompositionLocalOf
val LocalAppGraph = staticCompositionLocalOf<AppGraph> {
error("AppGraph not provided. Wrap content in CompositionLocalProvider(LocalAppGraph provides ...).")
}