Android:使用 kotlin 流时 Firebase 对象为空
Android: Firebase Object is null when using kotlin flow
我的问题是,当我尝试从我的数据库中获取文档时,该文档又名对象始终为空。我只有在使用 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)
}
获取数据的Viewmodel
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 是否为 null。
然后我用 val price = MutableLiveData(emailEntity.value?.basePrice ?: 1000F)
设置价格但是因为 emailentity
是 null
所以价格总是 1000
编辑 2
我现在对问题进行了进一步的研究,向前迈进了一大步。当从像 CalibrateRepairMessageFragment
这样的片段观察 emailEntity
时, 值不再是 null
.
此外,在观察emailEntity
时,值在viewModel
中也不是null
,而是在一个片段中观察到的!那么我如何从我的 viewModel
中观察 emailEntity
或从我的 repository
中获取值并在我的 viewmodel
中使用它?
好的,我的问题已经解决了,这是最终的解决方案:
状态class
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()
}
我的问题是,当我尝试从我的数据库中获取文档时,该文档又名对象始终为空。我只有在使用 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)
}
获取数据的Viewmodel
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 是否为 null。
然后我用 val price = MutableLiveData(emailEntity.value?.basePrice ?: 1000F)
设置价格但是因为 emailentity
是 null
所以价格总是 1000
编辑 2
我现在对问题进行了进一步的研究,向前迈进了一大步。当从像 CalibrateRepairMessageFragment
这样的片段观察 emailEntity
时, 值不再是 null
.
此外,在观察emailEntity
时,值在viewModel
中也不是null
,而是在一个片段中观察到的!那么我如何从我的 viewModel
中观察 emailEntity
或从我的 repository
中获取值并在我的 viewmodel
中使用它?
好的,我的问题已经解决了,这是最终的解决方案:
状态class
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()
}