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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 02:20:40