Android中Dagger Hilt环境下FirestoreImpl数据无法传递至Repository/ViewModel
问题分析与解决方案
你的Hilt配置本身没有直接导致数据为null的问题,但存在一处不合理配置;核心问题出在Firestore异步操作的错误处理以及LiveData/协程的误用上,以下是具体修复步骤:
一、修正Hilt中的不合理配置
删除AppModule中的provideMainActivity()方法:
@Module @InstallIn(SingletonComponent::class) object AppModule { @Singleton @Provides fun provideFirebaseAuth(): FirebaseAuth = Firebase.auth @Singleton @Provides fun provideFireStore(): FirebaseFirestore=FirebaseFirestore.getInstance() // 移除下面这行:单例持有Activity会导致内存泄漏,且你的代码未用到该实例 // @Singleton // @Provides // fun provideMainActivity(): MainActivity = MainActivity.getInstance() as MainActivity }
二、核心问题:异步操作处理错误
你在FirestoreServiceImpl中使用addSnapshotListener异步获取数据,但创建MutableLiveData后立刻返回,此时回调尚未执行,LiveData.value自然为null。上层Repo/ViewModel直接读取这个未填充的value,就会得到null。
方案1:单次获取数据(适合不需要实时更新的场景)
1. 重构FirestoreService接口
interface FirestoreService { val responseState: MutableStateFlow<Response> // 直接返回数据,而非LiveData suspend fun getAllCurrentBanner(): List<BannerModel>? }
2. 实现FirestoreServiceImpl
用Firestore的挂起函数await()等待异步结果,避免回调嵌套:
class FirestoreServiceImpl @Inject constructor(private val firestore: FirebaseFirestore) : FirestoreService { private val BANNER_COLLECTION = "BANNER" override val responseState: MutableStateFlow<Response> = MutableStateFlow(Response.NotInitialized) override suspend fun getAllCurrentBanner(): List<BannerModel>? { return try { responseState.value = Response.Loading // 用get() + await()挂起等待数据返回 val querySnapshot = firestore.collection(BANNER_COLLECTION) .whereEqualTo("isShow", true) .get() .await() responseState.value = Response.Success querySnapshot.toObjects(BannerModel::class.java) } catch (e: Exception) { responseState.value = Response.Error(e.message) null } } }
3. 重构FireStoreRepository
class FireStoreRepository @Inject constructor(private val firestoreService: FirestoreService){ fun responseState() = firestoreService.responseState suspend fun getAllBanner(): List<BannerModel>? { return firestoreService.getAllCurrentBanner() } }
4. 重构HomeScreenViewModel
使用viewModelScope管理协程,统一持有LiveData实例:
@HiltViewModel class HomeScreenViewModel @Inject constructor(val repository: FireStoreRepository): ViewModel() { // 统一持有LiveData,避免每次调用创建新实例 private val _banners = MutableLiveData<List<BannerModel>>(emptyList()) val banners: LiveData<List<BannerModel>> = _banners private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> Log.e("TAG1", "Error : ${throwable.localizedMessage}") } fun getAllBanner() { viewModelScope.launch(Dispatchers.IO + exceptionHandler) { val response = repository.getAllBanner() withContext(Dispatchers.Main) { _banners.value = response ?: emptyList() } } } }
5. 修改HomeScreen Composable
用LaunchedEffect触发一次数据请求,避免重组时重复调用:
@Composable fun HomeScreen( navController: NavController, homeScreenViewModel: HomeScreenViewModel = hiltViewModel() ) { // 页面初始化时触发一次数据请求 LaunchedEffect(Unit) { homeScreenViewModel.getAllBanner() } // 监听ViewModel中的LiveData val bannerListState by homeScreenViewModel.banners.observeAsState(emptyList()) Log.e("TAGbanner", "HomeScreen: $bannerListState", ) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { HorizontalPagerBanner(bannerListState) } }
方案2:实时监听数据变化(适合需要实时更新的场景)
如果需要监听Firestore数据的实时变化,改用Flow处理:
1. 重构FirestoreService接口
interface FirestoreService { val responseState: MutableStateFlow<Response> // 返回Flow供上层监听 fun getAllCurrentBannerFlow(): Flow<List<BannerModel>?> }
2. 实现FirestoreServiceImpl
用callbackFlow封装SnapshotListener:
class FirestoreServiceImpl @Inject constructor(private val firestore: FirebaseFirestore) : FirestoreService { private val BANNER_COLLECTION = "BANNER" override val responseState: MutableStateFlow<Response> = MutableStateFlow(Response.NotInitialized) override fun getAllCurrentBannerFlow(): Flow<List<BannerModel>?> { return callbackFlow { responseState.value = Response.Loading // 注册快照监听 val listener = firestore.collection(BANNER_COLLECTION) .whereEqualTo("isShow", true) .addSnapshotListener { value, error -> if (error != null) { responseState.value = Response.Error(error.message) close(error) return@addSnapshotListener } val banners = value?.toObjects(BannerModel::class.java) responseState.value = Response.Success trySend(banners) } // 协程取消时移除监听 awaitClose { listener.remove() } } } }
3. 重构FireStoreRepository
class FireStoreRepository @Inject constructor(private val firestoreService: FirestoreService){ fun responseState() = firestoreService.responseState fun getAllBannerFlow(): Flow<List<BannerModel>?> { return firestoreService.getAllCurrentBannerFlow() } }
4. 重构HomeScreenViewModel
@HiltViewModel class HomeScreenViewModel @Inject constructor(val repository: FireStoreRepository): ViewModel() { private val _banners = MutableLiveData<List<BannerModel>>(emptyList()) val banners: LiveData<List<BannerModel>> = _banners private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> Log.e("TAG1", "Error : ${throwable.localizedMessage}") } fun getAllBanner() { viewModelScope.launch(Dispatchers.IO + exceptionHandler) { repository.getAllBannerFlow().collect { banners -> withContext(Dispatchers.Main) { _banners.value = banners ?: emptyList() } } } } }
5. HomeScreen Composable同方案1
三、额外优化:修正BannerModel
将BannerModel改为data class,方便调试和Firestore序列化:
data class BannerModel( val bannerId: String? = "", val image: String? = "", val isShow: Boolean? = false, val title: String? = "", val shopId: String? = "", )
内容的提问来源于stack exchange,提问作者monjur
相关产品推荐
相关产品推荐

