【问题标题】:Android: Firebase Object is null when using kotlin flowAndroid:使用 kotlin 流时,Firebase 对象为空
【发布时间】:2021-01-06 01:47:38
【问题描述】:

我的问题是,当我尝试从我的数据库中获取文档时,该文档又名对象始终为空。只有当我使用 Kotlin Coroutines 将文档从数据库中取出时,我才会遇到这个问题。对侦听器使用标准方法确实有效。

电子邮件存储库

interface EmailRepository {
    suspend fun getCalibratePrice(): Flow<EmailEntity?>
    suspend fun getRepairPrice(): Flow<EmailEntity?>
}

EmailRepository 实现

class EmailRepositoryImpl @Inject constructor(private val db: FirebaseFirestore) : EmailRepository {

    fun hasInternet(): Boolean {
        return true
    }

    // This works! When using flow to write a document, the document is written!
    override fun sendEmail(email: Email)= flow {
        emit(EmailStatus.loading())
        if (hasInternet()) {
            db.collection("emails").add(email).await()
            emit(EmailStatus.success(Unit))
        } else {
            emit(EmailStatus.failed<Unit>("No Email connection"))
        }
    }.catch {
        emit(EmailStatus.failed(it.message.toString()))
    }.flowOn(Dispatchers.Main)


    // This does not work! "EmailEntity" is always null. I checked the document path!
    override suspend fun getCalibratePrice(): Flow<EmailEntity?> = flow {
        val result = db.collection("emailprice").document("Kalibrieren").get().await()
        emit(result.toObject<EmailEntity>())
    }.catch {

    }.flowOn(Dispatchers.Main)


    // This does not work! "EmailEntity" is always null. I checked the document path!
    override suspend fun getRepairPrice(): Flow<EmailEntity?> = flow {
        val result = db.collection("emailprice").document("Reparieren").get().await()
        emit(result.toObject<EmailEntity>())
    }.catch {

    }.flowOn(Dispatchers.Main)
}

我获取数据的视图模型

init {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                if (subject.value != null){
                    when(subject.value) {
                        "Test" -> {
                            emailRepository.getCalibratePrice().collect {
                                emailEntity.value = it
                            }
                        }
                        "Toast" -> {
                            emailRepository.getRepairPrice().collect {
                                emailEntity.value = it
                            }
                        }
                    }
                }
            }
        }
    }

private val emailEntity = MutableLiveData<EmailEntity?>()

private val _subject = MutableLiveData<String>()
val subject: LiveData<String> get() = _subject

片段

@AndroidEntryPoint
class CalibrateRepairMessageFragment() : EmailFragment<FragmentCalibrateRepairMessageBinding>(
    R.layout.fragment_calibrate_repair_message,
) {
    // Get current toolbar Title and send it to the next fragment.
    private val toolbarText: CharSequence by lazy { toolbar_title.text }

    override val viewModel: EmailViewModel by navGraphViewModels(R.id.nav_send_email) { defaultViewModelProviderFactory }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Here I set the data from the MutableLiveData "subject". I don't know how to do it better
        viewModel.setSubject(toolbarText.toString())
    }
}

有人会说,Firebase 规则是这里的问题,但这里不应该是这种情况,因为数据库是开放的并且使用侦听器方法确实有效。

我从我的CalibrateRepairMessageFragment 获得了subject.value。当我不检查 if(subject.value != null) 时,我会从 init 块中获得 NullPointerException。

我只会在我的 viewModel 中使用emailEntitiy,而不是在它之外。

感谢每一个帮助,谢谢。

编辑

这是我获取数据的新方法。该对象仍然为空!我还在挂起函数中添加了Timber.d 消息,这些消息也永远不会被执行,因此流永远不会引发错误。使用这种新方法,我不再得到 NullPointerException

private val emailEntity = liveData {
    when(subject.value) {
        "Test" -> emailRepository.getCalibratePrice().collect {
            emit(it)
        }
        "Toast" -> emailRepository.getRepairPrice().collect {
            emit(it)
        }
        // Else block is never executed, therefore "subject.value" is either Test or toast and the logic works. Still error when using flow!
        else -> EmailEntity("ERROR", 0F)
    }
}

我在我的一个函数中使用Timber.d("EmailEntity is ${emailEntity.value}") 检查 emailEntity 是否为空。

然后我用val price = MutableLiveData(emailEntity.value?.basePrice ?: 1000F) 设置价格,但因为emailentitynull,所以价格总是1000

编辑 2

我现在已经进一步研究了这个问题,并向前迈出了一大步。从像CalibrateRepairMessageFragment 这样的片段观察emailEntity 时,值不再是 null

此外,当观察emailEntity 时,值也不是 null in viewModel,但只有在一个片段中观察到它时!那么如何从我的viewModel 观察emailEntity 或从我的repository 获取值并在我的viewmodel 中使用它?

【问题讨论】:

    标签: android kotlin google-cloud-firestore android-livedata android-mvvm


    【解决方案1】:

    好的,我的问题已经解决了,这是最终的解决方案:

    状态类

    sealed class Status<out T> {
        data class Success<out T>(val data: T) : Status<T>()
        class Loading<T> : Status<T>()
        data class Failure<out T>(val message: String?) : Status<T>()
    
        companion object {
            fun <T> success(data: T) = Success<T>(data)
            fun <T> loading() = Loading<T>()
            fun <T> failed(message: String?) = Failure<T>(message)
        }
    }
    

    电子邮件存储库

    interface EmailRepository {
        fun sendEmail(email: Email): Flow<Status<Unit>>
        suspend fun getCalibratePrice(): Flow<Status<CalibrateRepairPricing?>>
        suspend fun getRepairPrice(): Flow<Status<CalibrateRepairPricing?>>
    }
    

    EmailRepositoryImpl

    class EmailRepositoryImpl (private val db: FirebaseFirestore) : EmailRepository {
        fun hasInternet(): Boolean {
            return true
        }
    
        override fun sendEmail(email: Email)= flow {
            Timber.d("Executed Send Email Repository")
            emit(Status.loading())
            if (hasInternet()) {
                db.collection("emails").add(email).await()
                emit(Status.success(Unit))
            } else {
                emit(Status.failed<Unit>("No Internet connection"))
            }
        }.catch {
            emit(Status.failed(it.message.toString()))
        }.flowOn(Dispatchers.Main)
    
        // Sends status and object to viewModel
        override suspend fun getCalibratePrice(): Flow<Status<CalibrateRepairPricing?>> = flow {
            emit(Status.loading())
            val entity = db.collection("emailprice").document("Kalibrieren").get().await().toObject<CalibrateRepairPricing>()
            emit(Status.success(entity))
        }.catch {
            Timber.d("Error on getCalibrate Price")
            emit(Status.failed(it.message.toString()))
        }
    
        // Sends status and object to viewModel
        override suspend fun getRepairPrice(): Flow<Status<CalibrateRepairPricing?>> = flow {
            emit(Status.loading())
            val entity = db.collection("emailprice").document("Kalibrieren").get().await().toObject<CalibrateRepairPricing>()
            emit(Status.success(entity))
        }.catch {
            Timber.d("Error on getRepairPrice")
            emit(Status.failed(it.message.toString()))
        }
    }
    

    视图模型

    private lateinit var calibrateRepairPrice: CalibrateRepairPricing
    
    private val _calirateRepairPriceErrorState = MutableLiveData<Status<Unit>>()
    val calibrateRepairPriceErrorState: LiveData<Status<Unit>> get() = _calirateRepairPriceErrorState
    
    init {
            viewModelScope.launch {
                when(_subject.value.toString()) {
                    "Toast" -> emailRepository.getCalibratePrice().collect {
                        when(it) {
                            is Status.Success -> {
                                calibrateRepairPrice = it.data!!
                                _calirateRepairPriceErrorState.postValue(Status.success(Unit))
                            }
                            is Status.Loading -> _calirateRepairPriceErrorState.postValue(Status.loading())
                            is Status.Failure -> _calirateRepairPriceErrorState.postValue(Status.failed(it.message))
                        }
                    }
                    else -> emailRepository.getRepairPrice().collect {
                        when(it) {
                            is Status.Success -> {
                                calibrateRepairPrice = it.data!!
                                _calirateRepairPriceErrorState.postValue(Status.success(Unit))
                            }
                            is Status.Loading -> _calirateRepairPriceErrorState.postValue(Status.loading())
                            is Status.Failure -> _calirateRepairPriceErrorState.postValue(Status.failed(it.message))
                        }
                    }
                }
                price.postValue(calibrateRepairPrice.head!!.basePrice)
            }
        }
    

    您现在可以观察其中一个片段的状态(但您不需要!)

    片段

    viewModel.calibrateRepairPriceErrorState.observe(viewLifecycleOwner) { status ->
                when(status) {
                    is Status.Success -> requireContext().toast("Price successfully loaded")
                    is Status.Loading -> requireContext().toast("Price is loading")
                    is Status.Failure -> requireContext().toast("Error, Price could not be loaded")
                }
            }
    

    这是我的 toast 扩展功能:

    fun Context.toast(text: String, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(this, text, duration).show()
    }
    

    【讨论】:

      猜你喜欢
      • 2016-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-27
      • 2018-12-08
      • 2019-07-09
      相关资源
      最近更新 更多