架构

了解 MVVM、Repository、Hilt、导航和控制器如何协同工作。

Kotlin 模板采用常规 Compose 架构:由 Hilt 创建 controllers 和 repositories,ViewModels 暴露 StateFlow,stateless screen composables 渲染,Navigation Compose 使用 typed routes,Supabase 外面包一层小的 remote data sources。

形状

Composable Route
  -> @HiltViewModel
  -> repository/controller
  -> remote data source or platform SDK
  -> AppResult / StateFlow
  -> stateless Screen

目标不是重抽象,而是让每层职责清楚:screens 渲染,ViewModels 协调 UI state,repositories 标准化数据结果,controllers 拥有 app-wide services。

依赖注入

Hilt entry points:

Type职责
@HiltAndroidApp SoarAppApplication graph 和 eager startup hooks
@AndroidEntryPoint MainActivity注入 root Compose tree 使用的 controllers
@HiltViewModel把 repositories/controllers 注入 screen view models
@Singleton controllers/repositoriessession、purchases、DataStore-backed controllers、analytics、toasts 等共享服务

core/di/ 当前包含:

Module提供或绑定
CoroutineModule@ApplicationScope CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
DataStoreModule名为 soar_preferences 的 Preferences DataStore
RepositoryModuleSupabase remote data source interfaces 和 Google Sign-In controller binding
SessionModule来自 SessionController.currentUserFlow<UserInfo?>
BiometricModuleBiometricSettings interface 到 BiometricController

SoarApp.onCreate() 当前启动 Sentry、PostHog 和 update controller。RevenueCat 被注入到这里, 让 singleton 被构造并开始观察 session state。

MVVM 和 state

Feature folders 使用 route/screen split:

@Composable
fun TodosRoute(viewModel: TodosViewModel = hiltViewModel()) {
    val state by viewModel.uiState.collectAsState()
    TodosScreen(state = state, onAdd = viewModel::add)
}

TodosViewModel 是参考模式:data class TodosUiState、private MutableStateFlow、public StateFlow,以及在 viewModelScope 中执行的事件方法。

当前代码同时使用 collectAsState()collectAsStateWithLifecycle()。新 route composables 收集 ViewModel state 时优先使用 collectAsStateWithLifecycle();root controllers 可沿用现有 plain collectAsState() 的写法。

Repository 边界

Repositories 用 AppResult<T> 包裹 backend 调用:

sealed interface AppResult<out T> {
    data class Success<T>(val data: T) : AppResult<T>
    data class Failure(val error: AppError) : AppResult<Nothing>
}

appSuspendRunCatching 会保留 coroutine cancellation,把常见 Supabase 和网络失败映射成友好消息, 并避免 exception 泄漏出 repository boundary。

Repository pattern:

RemoteDataSource interface
  -> SupabaseRemoteDataSource implementation
  -> Repository
  -> ViewModel

Unit tests 使用 in-memory fake remote data sources 实例化 repositories。项目不使用 Mockk, unit tests 也不需要 Hilt。

导航

Routes 是 nav/Routes.kt 中的 Kotlin serialization objects/data classes:

@Serializable
data object Paywall

@Serializable
data class LegalDocument(val docId: String)

AppNavGraph.kt 拥有:

  • UpdateGate -> OnboardingGate -> ConsentGate 组合。
  • 外层 NavHostMainShell、auth graph、paywall、refer、upload、subscription、redeem-code。
  • private MainNavGraph composable:消费 deep links 和 auth state。

MainShellScreen.kt 拥有 Home、Component、Showcase、Me 的 nested tab NavHost。Tab 切换使用 popUpTo(findStartDestination())saveState = truelaunchSingleTop = truerestoreState = true,所以每个 tab 会保留自己的 back stack。

Gates 是 composition wrappers,不是 route destinations。这样可以清楚区分哪些代码在导航前阻塞应用, 哪些代码参与导航本身。

并发

使用最小且合适的 scope:

工作Scope
UI actions 和 screen loadingviewModelScope
长生命周期 app controllersCoroutineModule 提供的 @ApplicationScope
DataStore-backed controllersstateIn(applicationScope, SharingStarted.Eagerly, ...)
Root UI callbackshosting composable 中的 rememberCoroutineScope()

示例:

  • SessionController 和 entitlement/update/locale/theme controllers 在 application scope 中维护 app-wide StateFlow
  • TodosViewModel 会在 signed-in user 变化时取消并替换 load job。
  • NotificationsRepository 作为 singleton 保存 push token 和 inbox StateFlow,因为 FCM events 可能在 screen 不可见时到达。

测试

命令:

./gradlew testDebugUnitTest
./gradlew connectedDebugAndroidTest

当前约定:

  • Unit tests 位于 app/src/test
  • Coroutine tests 使用 kotlinx-coroutines-testrunTest 和共享 MainDispatcherRule
  • Repository tests 使用 fake remote data sources。
  • ViewModel tests 用 fakes 直接调用 constructor。
  • Instrumented tests 位于 app/src/androidTest,需要连接设备或模拟器。

添加 feature

  1. 如果 feature 有持久化数据,在 data/model 添加 model。
  2. 添加 remote data source interface 和 Supabase implementation。
  3. core/di/RepositoryModule 绑定实现。
  4. 添加返回 AppResult 的 repository。
  5. 添加一个拥有单一 UI state 的 @HiltViewModel
  6. 添加 route composable 收集 state,并添加接收 values/callbacks 的 stateless screen composable。
  7. Routes.kt 或 feature-local graph 注册 typed routes。
  8. 先添加 fake-backed unit tests,再接入真实服务行为。

相关页面

On this page