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

如何基于Firestore异步结果更新Android UI并优化请求

问题描述

我在Kotlin中编写Cloud Firestore数据库查询,请求本身可正常执行,但异步流程处理存在问题。目标是批量获取数据并封装为对象,待所有对象加载完成后再继续后续流程,以便在UI中展示数据。

数据库结构

  • 存在users集合
    • 每个用户拥有专属Recipes集合
      • 每个recipe下包含steps集合和ingredients集合

当前代码问题

应用启动后直接调用loadRecipes()请求数据,紧接着执行initUiElements()显示RecyclerView,但此时数据尚未加载完成,导致UI展示空数据。另外当前每个recipe需要3次数据库请求,希望能优化请求次数,同时实现异步处理,让UI能感知数据变化后更新,或等待请求完成再显示界面。刚接触协程,在Firebase文档中未找到相关参考信息。

主Activity代码

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        loadRecipes()//加载数据库信息
        initUiElements()//在RecyclerView中展示信息
    }

FirebaseRecipes类的loadRecipes方法

fun loadRecipes(){

        //第一次请求:获取Recipe名称和时长
        Log.d("FirebaseRecipes", "loadRecipes()")
        db.collection("users").document(userID.toString()).collection("Recipes").get()
            .addOnSuccessListener { result ->
                for (document in result) {
                    val duration = document.data["duration"].toString()
                    val name = document.data["name"].toString()
                    var recipe = Recipe(name, duration)
                    RecipesList.add(recipe)

                    //第二次请求:获取食材信息
                    db.collection("users").document(userID.toString()).collection("Recipes").document(document.id).collection("ingredients").get()
                        .addOnSuccessListener { result ->
                            for (document in result) {
                                val amount = document.data["amount"].toString()
                                val name = document.data["name"].toString()
                                val barcode = document.data["unit"].toString()
                                val ingredient = Ingredient(name, amount, barcode)
                                recipe.ingredients.add(ingredient)
                            }
                        }

                    //第三次请求:获取步骤信息
                 db.collection("users").document(userID.toString()).collection("Recipes").document(document.id).collection("steps").get()
                        .addOnSuccessListener { result ->
                            for (document in result) {
                                val step = document.data["text"].toString()
                                recipe.steps.add(step)
                            }
                        }
                }
            }
    }
解决方案

在@AlexMamo、@UsmanMahmood和@NguyễnMinhKhoa的帮助下,得到以下优化方案:

1. 自定义响应状态类

通过密封类封装数据加载的不同状态(加载中、成功、错误),便于UI层根据状态做出对应处理:

sealed class CustomResponse<T>(val data: T? = null, val errorMessage: String? = null) {
    class Loading<T> : CustomResponse<T>()
    class Success<T>(data: T? = null) : CustomResponse<T>(data = data)
    class Error<T>(errorMessage: String) : CustomResponse<T>(errorMessage = errorMessage)
}

2. 创建ViewModel管理数据

用ViewModel持有LiveData,统一管理Recipe列表的状态,确保数据在配置变更时不丢失:

private val _recipeList =
        MutableLiveData<CustomResponse<List<Recipe>>>(CustomResponse.Loading())
    val recipeList: LiveData<CustomResponse<List<Recipe>>>
        get() = _recipeList

    fun shareRecipe(recipes: List<Recipe>) {
        _recipeList.value = CustomResponse.Success(recipes)
    }

3. 优化Firebase数据获取

将原来的3次请求合并为1次,直接从Recipe文档中读取嵌套的steps和ingredients数据,减少数据库请求次数,并将加载完成的数据更新到ViewModel:

fun loadRecipes(activity: Activity) {
        val model =
            ViewModelProvider(activity as ViewModelStoreOwner)[ViewModel::class.java]

        db.collection("users").document(userID.toString()).collection("Recipes").get()
            .addOnSuccessListener { result ->
                val recipesList = ArrayList<Recipe>()
                for (document in result) {
                    val duration = document.data["duration"].toString()
                    val name = document.data["name"].toString()
                    var recipe = Recipe(name, duration)
                    recipesList.add(recipe)

                    recipe.steps = document.data["steps"] as ArrayList<String>

                    val ingredients =
                        document.data["ingredients"] as ArrayList<HashMap<String, Any>>
                    for (ingredient in ingredients) {
                        val name = ingredient["name"].toString()
                        val amount = (ingredient["amount"] as Long).toInt()
                        val ingredient = Ingredient(name, amount)
                        recipe.ingredients.add(ingredient)
                    }
                }
                model.shareRecipe(recipesList)
            }
}

4. UI层监听数据变化

在Fragment中观察ViewModel的LiveData,根据不同状态更新UI,比如显示加载进度条、展示数据或错误提示:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val model = ViewModelProvider(requireActivity())[RecipesViewModel::class.java]
        model.recipeList.observe(viewLifecycleOwner) { response ->
            when (response as CustomResponse<List<Recipe>>) {
                is CustomResponse.Loading -> {
                    rvRecipes.visibility = View.INVISIBLE
                    progressBar.visibility = View.VISIBLE
                }
                is CustomResponse.Success -> {
                    rvRecipes.visibility = View.VISIBLE
                    progressBar.visibility = View.GONE

                    Log.d("FirebaseRecipes", "Recipes loaded within Fragment")

                    adapter.content = response.data as ArrayList<Recipe>
                    adapter.notifyDataSetChanged()
                }
                is CustomResponse.Error -> {
                    response.errorMessage?.let {
                        rvRecipes.visibility = View.INVISIBLE
                        //TODO: 展示错误提示
                    }
                }
            }
        }
}

内容的提问来源于stack exchange,提问作者Pantastix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 16:33:15