refactor(viewmodel): 将 searchFoodList/getCookFoodList 迁移至 StateFlow,DishListFragment 改用 combine 并行合并数据并修复分页本地数据混入问题

This commit is contained in:
2026-04-23 09:54:34 +08:00
parent 16fe64e9fe
commit 9e99e6ef6d
9 changed files with 246 additions and 269 deletions
@@ -6,6 +6,9 @@ import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.SeasoningEntity import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -42,16 +45,19 @@ class NetViewModel(
} }
} }
/**
* 搜索菜品列表的 UI 状态流,UI 层通过 collect 监听
*/
private val _searchFoodState = MutableStateFlow<UiState<MutableList<FoodRecord>?>>(UiState.Idle)
val searchFoodState: StateFlow<UiState<MutableList<FoodRecord>?>> = _searchFoodState.asStateFlow()
/** /**
* 搜索菜品列表 * 搜索菜品列表
*/ */
fun searchFoodList( fun searchFoodList(param: MutableMap<String, Any>) {
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch { viewModelScope.launch {
repository.searchFoodList(param, onSuccess, onFailure) _searchFoodState.value = UiState.Loading
_searchFoodState.value = repository.searchFoodList(param)
} }
} }
@@ -46,18 +46,18 @@ class RemoteRepository {
/** /**
* 搜索菜品列表 * 搜索菜品列表
* @param param 查询参数 * @param param 查询参数
* @param onSuccess 成功回调 * @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
* @param onFailure 失败回调,参数为 (errorCode, errorMsg)
*/ */
suspend fun searchFoodList( suspend fun searchFoodList(param: MutableMap<String, Any>): UiState<MutableList<FoodRecord>?> {
param: MutableMap<String, Any>, return try {
onSuccess: (MutableList<FoodRecord>?) -> Unit, val resp = apiService.searchFoodList(param = param)
onFailure: (String, String) -> Unit if (resp.isSuccess()) UiState.Success(resp.data)
) = request( else UiState.Error(resp.code, resp.msg ?: "")
onRequest = { apiService.searchFoodList(param = param) }, } catch (e: Exception) {
onSuccess = onSuccess, val ex = getApiException(e)
onFailure = onFailure UiState.Error("-1", ex.errorMsg)
) }
}
/** /**
* 查询采样数据列表 * 查询采样数据列表
@@ -0,0 +1,15 @@
package com.shuwei.dish.match.net
/**
* 统一 UI 状态模型,用于 ViewModel 向 UI 层传递请求状态
* Idle — 初始状态,未发起请求
* Loading — 请求进行中
* Success — 请求成功,携带数据
* Error — 请求失败,携带错误码和错误信息
*/
sealed class UiState<out T> {
object Idle : UiState<Nothing>()
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val code: String, val msg: String) : UiState<Nothing>()
}
@@ -107,8 +107,8 @@ class DishSamplingActivity : BaseActivity() {
ViewModelProvider(this, factory)[AppViewModel::class.java] ViewModelProvider(this, factory)[AppViewModel::class.java]
} }
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) { fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 1) { action(it) } appViewModel.getCookFoodList(cookMode = 1)
} }
/** /**
@@ -4,6 +4,9 @@ import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.FoodRecordAdapter import com.shuwei.dish.match.adapter.FoodRecordAdapter
@@ -12,6 +15,7 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.entity.FoodRecord import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.KeyboardUtil import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.gone
@@ -19,6 +23,7 @@ import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible import com.shuwei.dish.match.utils.ext.visible
import java.io.Serializable import java.io.Serializable
import kotlinx.coroutines.launch
class FoodSearchActivity : BaseActivity() { class FoodSearchActivity : BaseActivity() {
@@ -83,6 +88,31 @@ class FoodSearchActivity : BaseActivity() {
adapter = recordAdapter adapter = recordAdapter
} }
addViewListener() addViewListener()
initObserver()
}
/**
* 收集 searchFoodState,统一处理 Loading / Success / Error 状态
*/
private fun initObserver() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
netViewModel.searchFoodState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> handleSearchResult(state.data)
is UiState.Error -> {
delayDismissLoading()
finishRefresh()
binding.refreshLayout.setEnableRefresh(true)
toast(state.msg)
if (pageNo == 1) loadEmptyView()
}
is UiState.Idle -> {}
}
}
}
}
} }
private fun addViewListener() { private fun addViewListener() {
@@ -140,61 +170,6 @@ class FoodSearchActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun queryListInfo(input: String) { private fun queryListInfo(input: String) {
showLoading()
// val map = mutableMapOf(
// "foodName" to input.trim(),
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize",
// "canteenId" to BaseApp.canteenId
// "dinnerType" to getDinnerTypeText()
// )
// val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
// append("?")
// map.forEach { (key, value) -> append("$key=$value&") }
// }
// sb.deleteCharAt(sb.length - 1)
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = {
// delayDismissLoading()
// finishRefresh()
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// val json = it.toJsonString()
// val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
// if (recordBean == null || recordBean.records.isNullOrEmpty()) {
//// toast("暂未搜索到相关菜品信息")
// if (pageNo == 1) {
// loadEmptyView()
// }
// return@get
// }
// val records = recordBean.records
// if (pageNo == 1) {
// list.clear()
// }
// list.addAll(records!!)
// recordAdapter.notifyDataSetChanged()
// val isLoadMoreEnable = records.size >= pageSize
// binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
// if (isLoadMoreEnable) {
// pageNo++
// }
// },
// doFailure = { code, msg ->
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// toast(msg)
// delayDismissLoading()
// finishRefresh()
// if (pageNo == 1) {
// loadEmptyView()
// }
// }
// )
val map = mutableMapOf<String, Any>( val map = mutableMapOf<String, Any>(
"foodName" to input.trim(), "foodName" to input.trim(),
"pageNum" to pageNo, "pageNum" to pageNo,
@@ -202,40 +177,27 @@ class FoodSearchActivity : BaseActivity() {
"placeId" to BaseApp.canteenId, "placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText() "dinnerType" to getDinnerTypeText()
) )
netViewModel.searchFoodList( netViewModel.searchFoodList(param = map)
param = map, }
onSuccess = {
/**
* 处理搜索结果,填充列表或展示空视图
*/
@SuppressLint("NotifyDataSetChanged")
private fun handleSearchResult(records: MutableList<FoodRecord>?) {
delayDismissLoading() delayDismissLoading()
finishRefresh() finishRefresh()
binding.refreshLayout.setEnableRefresh(true) binding.refreshLayout.setEnableRefresh(true)
if (it.isNullOrEmpty()) { if (records.isNullOrEmpty()) {
// toast("暂未搜索到相关菜品信息") if (pageNo == 1) loadEmptyView()
if (pageNo == 1) { return
loadEmptyView()
} }
return@searchFoodList if (pageNo == 1) list.clear()
} list.addAll(records)
if (pageNo == 1) {
list.clear()
}
list.addAll(it)
recordAdapter.notifyDataSetChanged() recordAdapter.notifyDataSetChanged()
val isLoadMoreEnable = it.size >= pageSize val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable) binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) { if (isLoadMoreEnable) pageNo++
pageNo++
}
}, onFailure = { code, msg ->
binding.refreshLayout.run {
setEnableRefresh(true)
}
toast(msg)
delayDismissLoading()
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
})
} }
private fun finishRefresh() { private fun finishRefresh() {
@@ -5,7 +5,10 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R import com.shuwei.dish.match.R
@@ -27,6 +30,7 @@ import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory import com.shuwei.dish.match.viewmodel.factory.AppFactory
import java.io.Serializable import java.io.Serializable
import kotlinx.coroutines.launch
class SamplingListActivity : BaseActivity() { class SamplingListActivity : BaseActivity() {
@@ -47,10 +51,45 @@ class SamplingListActivity : BaseActivity() {
initViewModel() initViewModel()
initRecyclerView() initRecyclerView()
addViewListener() addViewListener()
initObserver()
requestData(true) requestData(true)
onBackPressedDispatcher.addCallback(this) { } onBackPressedDispatcher.addCallback(this) { }
} }
/**
* 收集 cookFoodListState,将本地烹饪中数据渲染到列表
*/
private fun initObserver() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
appViewModel.cookFoodListState.collect { cookFoodEntities ->
delayDismissLoading()
if (cookFoodEntities.isNullOrEmpty()) {
loadEmptyView()
return@collect
}
val tempList = mutableListOf<FoodRecord>()
cookFoodEntities.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
.forEach { entity ->
tempList.add(FoodRecord().apply {
foodId = entity.foodId
foodName = entity.foodName
cookMode = 1
isCooking = true
})
}
list.clear()
list.addAll(tempList)
dishAdapter.notifyDataSetChanged()
binding.refreshLayout.run {
setEnableRefresh(false)
setEnableLoadMore(false)
}
}
}
}
}
private fun setTitleBar() { private fun setTitleBar() {
setTitleBar(titleBarAction = { setTitleBar(titleBarAction = {
@@ -78,8 +117,8 @@ class SamplingListActivity : BaseActivity() {
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java] appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
} }
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) { fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 1) { action(it) } appViewModel.getCookFoodList(cookMode = 1)
} }
fun deleteCookFoodAndGoods(foodId: String, action: () -> Unit) { fun deleteCookFoodAndGoods(foodId: String, action: () -> Unit) {
@@ -298,32 +337,7 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun refreshLocalData() { fun refreshLocalData() {
showLoading() showLoading()
//查询本地数据 getCookFoodList()
getCookFoodList { cookFoodEntities ->
delayDismissLoading()
if (cookFoodEntities.isNullOrEmpty()) {
loadEmptyView()
return@getCookFoodList
}
val tempList = mutableListOf<FoodRecord>()
cookFoodEntities.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
.forEach { entity ->
tempList.add(FoodRecord().apply {
foodId = entity.foodId
foodName = entity.foodName
cookMode = 1
isCooking = true
})
}
list.clear()
list.addAll(tempList)
dishAdapter.notifyDataSetChanged()
binding.refreshLayout.run {
setEnableRefresh(false)
setEnableLoadMore(false)
}
}
} }
private fun requestData(isCooking: Boolean) { private fun requestData(isCooking: Boolean) {
@@ -10,8 +10,6 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivitySelectDishBinding import com.shuwei.dish.match.databinding.ActivitySelectDishBinding
import com.shuwei.dish.match.db.AppRepository import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.CommonDialog import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.ui.fragment.DishListFragment import com.shuwei.dish.match.ui.fragment.DishListFragment
import com.shuwei.dish.match.utils.KeyboardUtil import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
@@ -32,7 +30,8 @@ class SelectDishActivity : BaseActivity() {
private lateinit var binding: ActivitySelectDishBinding private lateinit var binding: ActivitySelectDishBinding
private lateinit var appViewModel: AppViewModel lateinit var appViewModel: AppViewModel
private set
private var dinnerType: String = "1" private var dinnerType: String = "1"
@@ -72,8 +71,8 @@ class SelectDishActivity : BaseActivity() {
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java] appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
} }
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) { fun getCookFoodList() {
appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType) { action(it) } appViewModel.getCookFoodList(cookMode = 0, dinnerType = dinnerType)
} }
private var configFinished = false private var configFinished = false
@@ -189,16 +188,8 @@ class SelectDishActivity : BaseActivity() {
isVisible = false isVisible = false
} }
public fun getFoodList( public fun getFoodList(param: MutableMap<String, Any>) {
param: MutableMap<String, Any>, netViewModel.searchFoodList(param = param)
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.searchFoodList(
param = param,
onSuccess = onSuccess,
onFailure = onFailure
)
} }
} }
@@ -5,6 +5,9 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick import com.chad.library.adapter4.util.setOnDebouncedItemClick
@@ -13,7 +16,9 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.BaseFragment import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentDishListBinding import com.shuwei.dish.match.databinding.FragmentDishListBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.ui.PrepareCookActivity import com.shuwei.dish.match.ui.PrepareCookActivity
import com.shuwei.dish.match.ui.SelectDishActivity import com.shuwei.dish.match.ui.SelectDishActivity
import com.shuwei.dish.match.ui.SubmitFoodActivity import com.shuwei.dish.match.ui.SubmitFoodActivity
@@ -21,6 +26,8 @@ import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.toast
import java.io.Serializable import java.io.Serializable
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
class DishListFragment : BaseFragment<FragmentDishListBinding>() { class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@@ -108,6 +115,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
itemTouchHelper.attachToRecyclerView(this) itemTouchHelper.attachToRecyclerView(this)
} }
addViewListener() addViewListener()
initObserver()
pageNo = 1 pageNo = 1
activity.showLoading() activity.showLoading()
@@ -115,6 +123,39 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
binding.refreshLayout.setEnableRefresh(true) binding.refreshLayout.setEnableRefresh(true)
} }
/**
* 用 combine 同时监听网络结果与本地数据,网络成功时统一合并渲染
* pageNo > 1(加载更多)时 cookFoodListState 不参与合并,直接追加网络数据
*/
private fun initObserver() {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
combine(
activity.netViewModel.searchFoodState,
activity.appViewModel.cookFoodListState
) { networkState, localList -> Pair(networkState, localList) }
.collect { (networkState, localList) ->
when (networkState) {
is UiState.Loading -> activity.showLoading()
is UiState.Success -> {
activity.delayDismissLoading()
finishRefresh()
loadAndMergeDishList(networkState.data, localList)
}
is UiState.Error -> {
if (isAdded.not()) return@collect
toast(networkState.msg)
finishRefresh()
if (pageNo == 1) loadEmptyView()
activity.delayDismissLoading()
}
is UiState.Idle -> {}
}
}
}
}
}
private fun addViewListener() { private fun addViewListener() {
binding.refreshLayout.run { binding.refreshLayout.run {
setEnableRefresh(true) setEnableRefresh(true)
@@ -147,72 +188,15 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
fun getDishList() { fun getDishList() {
activity.showLoading() val param = mutableMapOf<String, Any>(
activity.getFoodList(
param = mutableMapOf(
"pageNum" to pageNo, "pageNum" to pageNo,
"pageSize" to pageSize, "pageSize" to pageSize,
"placeId" to BaseApp.canteenId, "placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText() "dinnerType" to getDinnerTypeText()
),
onSuccess = { records ->
activity.delayDismissLoading()
finishRefresh()
loadDishList(records)
},
onFailure = { code, msg ->
if (isAdded.not()) {
return@getFoodList
}
toast(msg)
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
activity.delayDismissLoading()
}
) )
// val map = mapOf( // 网络与本地并行触发,pageNo > 1 时不重复查询本地数据
//// "foodName" to input, activity.getFoodList(param = param)
// "dinnerType" to getDinnerTypeText(), if (pageNo == 1) activity.getCookFoodList()
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize",
// "canteenId" to BaseApp.canteenId
// )
// val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
// append("?")
// map.forEach { (key, value) -> append("$key=$value&") }
// }
// sb.deleteCharAt(sb.length - 1)
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = { data ->
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// activity.delayDismissLoading()
// finishRefresh()
// val json = data.toJsonString()
// val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
// loadDishList(recordBean)
// }, doFailure = { code, msg ->
// try {
// if (isAdded.not()) {
// return@get
// }
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// toast(msg)
// finishRefresh()
// if (pageNo == 1) {
// loadEmptyView()
// }
// activity.delayDismissLoading()
// } catch (e: Exception) {
// e.printStackTrace()
// }
// })
} }
private var emptyViewBinding: LayoutEmptyViewBinding? = null private var emptyViewBinding: LayoutEmptyViewBinding? = null
@@ -248,24 +232,41 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
} }
} }
/**
* 将网络数据与本地烹饪中数据合并后渲染列表
* pageNo > 1 时忽略本地数据,直接追加网络数据,避免 combine 带入旧的本地状态
*/
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun refreshLocalData(list: MutableList<FoodRecord>) { private fun loadAndMergeDishList(
activity.getCookFoodList { cookFoodEntities -> records: MutableList<FoodRecord>?,
if (cookFoodEntities.isNullOrEmpty()) { localList: MutableList<CookFoodEntity>?
return@getCookFoodList ) {
try {
if (isAdded.not()) return
// pageNo > 1 时不参与本地合并,避免 combine 带入旧的本地状态
val effectiveLocalList = if (pageNo == 1) localList else null
if (records.isNullOrEmpty() && effectiveLocalList.isNullOrEmpty()) {
if (pageNo == 1) loadEmptyView()
return
} }
val tempList = mutableListOf<FoodRecord>() if (pageNo == 1) list.clear()
cookFoodEntities.forEachIndexed { index, entity -> val mergedList = records?.toMutableList() ?: mutableListOf()
val food = list.firstOrNull { it.foodId == entity.foodId }
// 仅 pageNo == 1 时才有本地数据参与合并
if (!effectiveLocalList.isNullOrEmpty()) {
val cookingItems = mutableListOf<FoodRecord>()
effectiveLocalList.forEachIndexed { index, entity ->
val food = mergedList.firstOrNull { it.foodId == entity.foodId }
if (food != null) { if (food != null) {
mergedList.remove(food)
food.isCooking = true food.isCooking = true
food.sort = index food.sort = index
food.dinnerType = entity.dinnerType ?: "0" food.dinnerType = entity.dinnerType ?: "0"
tempList.add(food) cookingItems.add(food)
} else { } else {
//当前查询到的list不包括本地数据id,可能数据在很多页以后了,暂时只能直接构造数据 // 当前网络数据不包含该本地记录,直接构造
tempList.add( cookingItems.add(
FoodRecord( FoodRecord(
foodId = entity.foodId, foodId = entity.foodId,
foodName = entity.foodName, foodName = entity.foodName,
@@ -276,36 +277,20 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
) )
} }
} }
list.removeAll(tempList) mergedList.addAll(0, cookingItems)
list.addAll(0, tempList)
dishAdapter.notifyDataSetChanged()
}
} }
@SuppressLint("NotifyDataSetChanged") if (pageNo >= 2 && !localList.isNullOrEmpty()) {
private fun loadDishList(records: MutableList<FoodRecord>?) { //第2页起,从mergedList中移除localList中的同foodId数据
try { val localIds = localList.map { it.foodId }.toHashSet()
if (isAdded.not()) { mergedList.removeAll { it.foodId in localIds }
return
} }
if (records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息") list.addAll(mergedList)
if (pageNo == 1) {
loadEmptyView()
}
return
}
if (pageNo == 1) {
list.clear()
}
list.addAll(records)
dishAdapter.notifyDataSetChanged() dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize val isLoadMoreEnable = (records?.size ?: 0) >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable) binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) { if (isLoadMoreEnable) pageNo++
pageNo++
}
refreshLocalData(list)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -6,6 +6,9 @@ import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.CookFoodEntity import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.SeasoningEntity import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -26,14 +29,15 @@ class AppViewModel(private val rep: AppRepository) : ViewModel() {
} }
} }
private val _cookFoodListState = MutableStateFlow<MutableList<CookFoodEntity>?>(null)
val cookFoodListState: StateFlow<MutableList<CookFoodEntity>?> = _cookFoodListState.asStateFlow()
fun getCookFoodList( fun getCookFoodList(
cookMode: Int, cookMode: Int,
dinnerType: String = "0", dinnerType: String = "0"
action: (MutableList<CookFoodEntity>?) -> Unit
) { ) {
viewModelScope.launch { viewModelScope.launch {
val data = rep.getCookFoodList(cookMode, dinnerType) _cookFoodListState.value = rep.getCookFoodList(cookMode, dinnerType)
action(data)
} }
} }