You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Dagger Hilt实现跨模块全局单例注入DataStore实例

解决方案

根因说明

你出现该问题的核心原因是手动构造了多个CheckLocationIsSetDataStore实例,多个DataStore实例操作同一个本地存储文件会触发并发冲突,导致读写异常。你已经配置了Hilt的Singleton级别的依赖提供逻辑,只要通过注入方式获取实例,Hilt会自动保证全局唯一。

改造步骤

1. 为ViewModel注入DataStore实例

不要在Composable视图层直接操作DataStore,将数据操作逻辑放到ViewModel层,通过构造函数注入全局唯一的DataStore实例:

PickLocationViewModel 改造

@HiltViewModel
class PickLocationViewModel @Inject constructor(
    private val checkLocationIsSetDataStore: CheckLocationIsSetDataStore,
    // 你的其他原有依赖,比如导航器等
) : ViewModel() {
    // 保留你原有的location状态逻辑
    val location = MutableStateFlow<Location?>(null)

    // 可以封装保存位置的方法,也可以直接在Composable中调用注入的dataStore
    fun saveSelectedLocation() = viewModelScope.launch {
        location.value?.let {
            checkLocationIsSetDataStore.set(it)
        }
    }
}

SplashViewModel 改造

@HiltViewModel
class SplashViewModel @Inject constructor(
    private val checkLocationIsSetDataStore: CheckLocationIsSetDataStore,
    // 你的其他原有依赖
) : ViewModel() {
    // 暴露位置状态流给视图层
    val locationStatusFlow = checkLocationIsSetDataStore.get

    // 你的原有导航逻辑保留
}

2. 改造Composable代码,删除手动构造DataStore的逻辑

PickLocation模块MapScreen改造

删除手动构造DataStore的两行代码,直接调用ViewModel的能力:

@Composable
fun MapScreen(navigatorViewModel: PickLocationViewModel = hiltViewModel()) {
    // 删除这两行代码:
    // val context = LocalContext.current
    // val dataStore = CheckLocationIsSetDataStore(context = context)
    Surface(color = AppColor.ThemeColor.BACKGROUND) {
        val mapView = rememberMapViewWithLifecycle()
        Column(Modifier.fillMaxSize()) {
            Box(modifier = Modifier.fillMaxSize()) {
                MapViewContainer(mapView, navigatorViewModel)
                MapPinOverlay()
                Column(modifier = Modifier.align(Alignment.BottomCenter)) {
                    Button(
                        colors = ButtonDefaults.buttonColors(
                            backgroundColor = AppColor.brandColor.BLUE_DE_FRANCE,
                            contentColor = AppColor.neutralColor.DOCTOR
                        ),
                        onClick = {
                            navigatorViewModel.apply {
                                navigate(HomeDestination.route())
                                viewModelScope.launch {
                                    // 直接使用ViewModel中注入的DataStore
                                    checkLocationIsSetDataStore.set(location.value)
                                    // 也可以调用你封装好的saveSelectedLocation()方法
                                }
                            }
                        }) {
                        Text(
                            text = stringResource(R.string.confirm_address),
                            style = AppFont.PoppinsTypography.button
                        )
                    }
                    Spacer(modifier = Modifier.size(16.dp))
                }
            }
        }
    }
}

Splash模块SplashView改造

同样删除手动构造DataStore的代码,使用ViewModel暴露的状态流:

@OptIn(ExperimentalPermissionsApi::class)
@Composable
private fun SplashView(
    modifier: Modifier,
    multiplePermissionsState: MultiplePermissionsState,
    navigator: SplashViewModel = hiltViewModel()
) {
    // 删除这两行代码:
    // val context = LocalContext.current
    // val dataStore = CheckLocationIsSetDataStore(context = context)
    Box(
        modifier = modifier
            .fillMaxSize()
            .background(color = AppColor.brandColor.BLUE_DE_FRANCE)
            .padding(start = 64.dp, end = 64.dp, bottom = 16.dp)
    ) {
        Column(modifier = modifier.align(Alignment.Center)) {
            Image(
                painter = painterResource(id = R.drawable.mykuyatm),
                contentDescription = "mykuya tm image"
            )
            Image(
                painter = painterResource(id = R.drawable.mykuya_powered_by),
                contentDescription = "mykuya powered by image"
            )
        }
        Loading(modifier = modifier.align(Alignment.BottomCenter))
        FeatureThatRequiresPermission(
            multiplePermissionsState = multiplePermissionsState, permissionsState = {
                if (it) {
                    navigator.apply {
                        viewModelScope.launch {
                            delay(Constants.SPLASH_DELAY)
                            // 使用ViewModel中注入的DataStore暴露的流
                            locationStatusFlow.collect { model ->
                                model.let {
                                    if (model.latitude == Constants.IF_LOCATION_LAT_NOT_SET && model.longitude == Constants.IF_LOCATION_LNG_NOT_SET) {
                                        navigate(PickLocationDestination.route())
                                    }else{
                                        navigate(HomeDestination.route())
                                    }
                                }
                            }
                        }
                    }
                }
            })
    }
}

注意事项

  • 所有需要使用CheckLocationIsSetDataStore的模块,都要在gradle中添加对preferences模块的依赖,否则无法访问对应的类
  • 全程不要手动调用CheckLocationIsSetDataStore(context)构造实例,所有实例都通过Hilt注入获取,才能保证全局唯一
  • 如果特殊场景需要在Composable中直接获取DataStore实例,可以通过Hilt EntryPoint获取,但是优先推荐将数据操作放到ViewModel层处理,更符合MVVM架构规范

内容的提问来源于stack exchange,提问作者Saeed Noshadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 22:15:04