Skip to content

状态管理

app/src/main/java/com/joker/kit/core/state 当前只维护 UserState。它集中保存登录结果、用户资料、Token 等关键信息,并通过 StateFlow 向任意页面实时推送。目录下的 di/AppStateModule.kt 为其注入 @ApplicationScope 协程,保证在应用进程生命周期内都能响应事件。若后续出现订单、购物车等其他需要全局共享的状态,亦可在该目录新增对应的状态持有者,沿用同样的模式。

核心职责

  • 全局单一数据源isLoggedInuserInfoauthStateFlowApplication 初始化时即加载本地缓存,之后任何页面订阅即可收到更新,避免 A 页修改资料后 B 页不同步的问题。
  • 桥接多数据来源UserState 同时依赖 AuthStoreRepository(Token/MMKV)、UserInfoStoreRepository(本地资料)与 UserInfoRepository(网络接口),统一封装持久化与同步流程。
  • 协程托管:通过 @ApplicationScope 在应用级 CoroutineScope 内收集网络 Flow、写入本地并推送 UI,外部调用无需关心线程与生命周期。
  • 与导航/基类联动:所有 BaseViewModel 子类都持有 userState,可直接查询登录状态以决定是否跳转、是否拦截路由,实现“全局登录态 + 局部 UI”联动。

初始化流程

UserState 不会在构造时立刻读取本地,而是由 Application 在完成基础依赖(如 MMKV)后手动调用 initialize()。Hilt 模块负责提供应用级协程:

kotlin
@Module
@InstallIn(SingletonComponent::class)
object AppStateModule {
    @ApplicationScope
    @Provides @Singleton
    fun providesApplicationScope(): CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Default)
}
kotlin
@HiltAndroidApp
class Application : Application() {
    @Inject lateinit var userState: UserState

    override fun onCreate() {
        super.onCreate()
        MMKVUtils.init(this)
        userState.initialize() // 等待 MMKV 准备好后再恢复登录状态
    }
}

完成初始化后,UserState 会读取本地的 AuthUser 信息,填充 StateFlow 并决定是否已登录,业务层无需重复读取存储或处理异常。

在页面中订阅与刷新

任何 BaseViewModel 子类都可以直接使用受保护的 userState。示例:

kotlin
@HiltViewModel
class ProfileViewModel @Inject constructor(
    navigator: AppNavigator,
    userState: UserState
) : BaseViewModel(navigator, userState) {
    val isLoggedIn: StateFlow<Boolean> = userState.isLoggedIn
    val userInfo: StateFlow<User?> = userState.userInfo

    fun refreshProfile() {
        userState.refreshUserInfo()
    }
}

界面层只需收集这些 StateFlow 即可实现响应式刷新:

kotlin
@Composable
fun ProfileRoute(viewModel: ProfileViewModel = hiltViewModel()) {
    val isLoggedIn by viewModel.isLoggedIn.collectAsState()
    val userInfo by viewModel.userInfo.collectAsState()

    if (!isLoggedIn) {
        LoginHint(onClick = { /* 跳转登录 */ })
    } else {
        Text(text = userInfo?.nickname.orEmpty())
    }
}

登录成功或用户修改昵称时,只需调用 userState 提供的写入方法,所有订阅者都会同时得到更新,无需手动通知多个页面。

写入策略示例

  • 登录成功:在登录接口成功后调用 updateUserState(auth, user),方法内部会先写入 AuthStoreRepository / UserInfoStoreRepository,再同步内存状态并把 isLoggedIn 置为 true
  • 刷新 Token:当检测到 Token 将过期时,可以调用 updateAuth(newAuth) 覆盖本地缓存,而无需重新获取用户资料。
  • 编辑资料:调用 updateUserInfo(newUser),既写入本地,也会更新 _userInfo + _userId,其他页面立刻收到最新头像/昵称。
  • 手动同步网络refreshUserInfo() 会在 ApplicationScope 内触发 UserInfoRepository.getPersonInfo(),并复用 ResultHandler.handleResultWithData 处理结果与异常。
  • 退出登录logout() 统一清理本地缓存并重置所有 StateFlow,UI 自动回到未登录状态。

扩展其他共享状态示例

当业务需要新增“跨页面共享状态”时(例如购物车角标、App 级配置等),可以在 core/state 下创建新的状态持有类,并参考 UserState 的依赖注入方式。以下示例演示如何新增 CartState,用于在多个页面同步购物车数量:

1. 创建状态类core/state/cart/CartState.kt

kotlin
@Singleton
class CartState @Inject constructor(
    private val cartRepository: CartRepository,
    @ApplicationScope private val appScope: CoroutineScope
) {
    private val _cartCount = MutableStateFlow(0)
    val cartCount: StateFlow<Int> = _cartCount.asStateFlow()

    fun observeCart() {
        appScope.launch {
            cartRepository.observeCart()
                .collect { list -> _cartCount.value = list.sumOf { it.count } }
        }
    }
}

2. 提供依赖:由于 CartState 带有 @Singleton@Inject 构造函数,Hilt 会自动生成依赖;只要 CartRepositoryApplicationScope 等依赖可被注入,即可在任意组件中直接声明 @Inject constructor(..., private val cartState: CartState)

3. 使用方式:在需要展示购物车数量的 ViewModel 中注入 CartState,并收集 cartCount

kotlin
@HiltViewModel
class CartBadgeViewModel @Inject constructor(
    navigator: AppNavigator,
    userState: UserState,
    private val cartState: CartState
) : BaseViewModel(navigator, userState) {
    val cartCount = cartState.cartCount

    init { cartState.observeCart() }
}

当新增其他共享状态时,只需替换仓库依赖与状态字段,即可获得与 UserState 一致的响应式体验。

API 参考

状态流

名称类型说明
isLoggedInStateFlow<Boolean>是否已登录,默认 false
userIdStateFlow<Long>当前用户 ID,未登录为 0L
authStateFlow<Auth?>当前 Token/过期时间等认证信息
userInfoStateFlow<User?>用户资料(头像、昵称、性别等),可能为 null

写入/操作方法

名称主要参数说明
initialize()-读取本地缓存初始化状态,需在 Application 中手动调用一次
updateUserState(auth, user)Auth, User登录成功后调用,写入本地并推送所有状态流
updateUserInfo(user)User编辑资料后调用,仅更新用户信息相关流
updateAuth(auth)AuthToken 刷新或更换设备时调用,同时保持登录态
logout()-清除本地缓存和内存状态,恢复到未登录
shouldRefreshToken()suspend fun读取本地标记判断 Token 是否需要刷新,可结合拦截器使用
refreshUserInfo()-触发网络请求刷新资料,内部使用 ResultHandler 自动处理 Loading/错误

借助 UserState,数据拉取与跨页面同步被收敛到一个文件中:登录页、个人中心、导航栏角标等都可以共享同一份响应式数据,避免重复编写“取本地缓存 -> 解析 -> 通知 UI”的代码,也让全局登录态更容易维护。