I am getting empty list while fetching data from FirebaseFirestore in Android(Kotlin and jetpack Compose)

huangapple go评论106阅读模式
英文:

I am getting empty list while fetching data from FirebaseFirestore in Android(Kotlin and jetpack Compose)

问题

我正在不断从Firebase Firestore获取数据时,得到了大小为0的列表,而实际上数据库中有数据。以下是要查看的代码:

模型...

data class Product(
    val productId: String,
    val title: String,
    val description: String,
    val price: Int,
    val image: Int?,
    val category: String,
    val isPopular: Boolean,
)

存储库实现...

override suspend fun getProduct(): Any = withContext(Dispatchers.IO) {
    return@withContext try {
        val documentReference = firestore
            .collection("Products")
            .document("")
        documentReference
            .get()
            .addOnSuccessListener { document ->
                if (document != null) {
                    val product = document.toObject(Product::class.java)

                    Log.d("Successful", "Products received")
                } else {
                    Log.d("ErrorGetting", document.toString())
                }
            }
            .addOnFailureListener {
                Log.d("ErrorGetting", it.toString())
            }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

ViewModel...

private val _getProductFlow = MutableStateFlow<List<Product>>(emptyList())
val getProductFlow: StateFlow<List<Product>> = _getProductFlow

suspend fun getProduct() = viewModelScope.launch {
    val result = fireRepository.getProduct()
    _getProductLiveData.value = listOf(result as Product)
}

组合函数...

val pro = viewModel.getProductFlow.collectAsState().value

Card(
    modifier = Modifier.fillMaxWidth(),
    shape = RoundedCornerShape(15.dp),
    elevation = CardDefaults.cardElevation(
        defaultElevation = 4.dp,
        hoveredElevation = 8.dp
    )
) {
    pro.forEach {
        Text(text = it.title, color = DarkTeal)
    }
}

我调试了pro变量,发现它的大小为0,而数据库中有3个集合。我也尝试了其他方法,但结果都是一样的。

这是init块...

init {
    CoroutineScope(Dispatchers.IO).launch {
        getProduct()
    }
}
英文:

i am constantly getting list of size 0 while fetching data from firebaseFirestore, which have data in it,
here is the code to take a look at...

Model...

data class Product(
    val productId: String,
    val title: String,
    val description: String,
    val price: Int,
    val image: Int?,
    val category: String,
    val isPopular: Boolean,
)

Repository implementation..

override suspend fun getProduct(): Any =  withContext(Dispatchers.IO) {
       return@withContext try{
            val documentReference = firestore
                .collection(&quot;Products&quot;)
                .document(&quot;&quot;)
            documentReference
                .get()
                .addOnSuccessListener { document -&gt;
                    if (document != null) {
                        val product = document.toObject(Product::class.java)

                        Log.d(&quot;Successful&quot;, &quot;Products received&quot;)
                    } else {
                        Log.d(&quot;ErrorGetting&quot;, document.toString())
                    }
                }
                .addOnFailureListener {
                    Log.d(&quot;ErrorGetting&quot;, it.toString())
                }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

ViewModel...

private val _getProductFlow = MutableStateFlow&lt;List&lt;Product&gt;&gt;(emptyList())
    val getProductFlow: StateFlow&lt;List&lt;Product&gt;&gt; = _getProductFlow

suspend fun getProduct() = viewModelScope.launch {
        val result = fireRepository.getProduct()
        _getProductLiveData.value = listOf(result as Product)
    }

Composable fun...

val pro = viewModel.getProductFlow.collectAsState().value

Card(
                modifier = Modifier.fillMaxWidth(),
                shape = RoundedCornerShape(15.dp),
                elevation = CardDefaults.cardElevation(
                    defaultElevation = 4.dp,
                    hoveredElevation = 8.dp
                )
            ) {
                pro.forEach {
                    Text(text = it.title, color = DarkTeal)
                }
            }

I debugged pro variable and it was 0 while i have 3 collections stored in the database. I have tried other ways too but the result is same

also here is the init block...

init {
        CoroutineScope(Dispatchers.IO).launch {
            getProduct()
        }
    }

答案1

得分: 1

这是要翻译的内容:

这是因为你在使用监听器,这些监听器会在你的函数已经返回之后返回一些结果,而你实际上并没有返回你检索到的文档。你将它赋值给一个变量,然后什么都没做,只是记录了一下。

在挂起函数中,你需要使用await()而不是使用监听器。如果你没有调用阻塞函数,你也不需要使用Dispatchers.IO

返回值要么是null,要么你需要返回其他东西来表示你的错误情况。

按照以下方式更改这个函数:

override suspend fun getProduct(): Any? {
   return@withContext try{
        val document = firestore
            .collection("Products")
            .document("")
            .get()
            .await()
        if (document != null) {
            Log.d("Successful", "Products received")
        } else {
            Log.d("ErrorGetting", "Null document returned")
        }
        document?.toObject(Product::class.java)
    } catch (e: Exception) {
        Log.d("ErrorGetting", it.toString())
        null
    }
}

你还有一些其他错误:

  • 在你的视图模型中,getProduct()不应该是一个suspend函数。这意味着它只会在产品被检索后才返回,并且直接返回结果。但实际上它只是启动了一个协程。它也不应该被命名为"get",因为它并没有返回它描述的内容。更合适的命名可能是"launchFetchProduct"之类的。

  • 永远不要使用CoroutineScope().launch。这会创建一个协程,它不会在适当的时候自动取消,从而导致资源和内存泄漏。通常情况下,你应该使用lifecycleScopeviewModelScope,或者你已经注入到类中的协程作用域。如果你真的想创建一个不应该被取消的协程,因为它没有持有引用或使用应该持续保持的资源,直到工作完成,那么GlobalScope是合适的。

实际上,你有这个只填充一个StateFlow的getProduct()函数有点复杂。你必须记住从类外部调用它,但你又必须避免多次调用它以避免浪费资源多次获取。你可以通过使用flow构建器和stateIn来避免所有这些以及MutableStateFlow:

val getProductFlow: StateFlow<List<Product>> = flow {
   val result = fireRepository.getProduct() as? Product
   if (result == null) {
       Log.e(TAG, "Could not load product. Null or invalid response")
       return@flow
   }
   emit(listOf(result))
}.stateIn(viewModelScope, emptyList())
英文:

It's because you're using listeners that will return some result in the future after your function has already returned, and you never actually return the document you retrieve. You assigned it to a variable and did nothing but log it.

In a suspend function, you need to use await() instead of using listeners. You also don't need Dispatchers.IO if you aren't calling blocking functions, which you are not doing.

The return value has to either be null, or you need to return something else to represent the error cases you have.

Change this function as shown:

override suspend fun getProduct(): Any? {
   return@withContext try{
        val document = firestore
            .collection(&quot;Products&quot;)
            .document(&quot;&quot;)
            .get()
            .await()
        if (document != null) {
            Log.d(&quot;Successful&quot;, &quot;Products received&quot;)
        } else {
            Log.d(&quot;ErrorGetting&quot;, &quot;Null document returned&quot;)
        }
        document?.toObject(Product::class.java)
    } catch (e: Exception) {
        Log.d(&quot;ErrorGetting&quot;, it.toString())
        null
    }
}

A couple other things you're doing wrong:

  • getProduct() in your view model should not be a suspend function. That implies that it will only return after the product is retrieved, and that it will directly return the result. But it is just launching a coroutine. It should also not be named "get" because it doesn't return what it describes. It would be more appropriate to name it something like "launchFetchProduct".

  • Never use CoroutineScope().launch. This creates a coroutine that will not be automatically cancelled when appropriate, leading to resource and memory leaks. Usually, you should be using a lifecycleScope or viewModelScope, or one that you have injected into a class. If you truly want to create a coroutine that should not be cancelled because it's not holding references or using resources that should be held indefinitely or until work is definitely complete, then GlobalScope is appropriate.

It's actually convoluted that you have this getProduct() function that only fills in a StateFlow. And you have to remember to call it from outside the class, but you somehow need to avoid calling it more than once to avoid wasting resources fetching it multiple times. You could avoid all this and the MutableStateFlow by using the flow builder and stateIn:

val getProductFlow: StateFlow&lt;List&lt;Product&gt;&gt; = flow {
       val result = fireRepository.getProduct() as? Product
       if (result == null) {
           Log.e(TAG, &quot;Could not load product. Null or invalid response&quot;)
           return@flow
       }
       emit(listOf(result))
    }.stateIn(viewModelScope, emptyList())

huangapple
  • 本文由 发表于 2023年8月9日 16:25:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76865885.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定