如何基于Firestore异步结果更新Android UI并优化请求
问题描述
我在Kotlin中编写Cloud Firestore数据库查询,请求本身可正常执行,但异步流程处理存在问题。目标是批量获取数据并封装为对象,待所有对象加载完成后再继续后续流程,以便在UI中展示数据。
数据库结构
- 存在
users集合- 每个用户拥有专属
Recipes集合- 每个recipe下包含
steps集合和ingredients集合
- 每个recipe下包含
- 每个用户拥有专属
当前代码问题
应用启动后直接调用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
相关产品推荐
相关产品推荐

