fix(food): 修复并发竞态、双重刷新及搜索态丢失问题
- FoodListFragment:添加 currentJob 取消机制,防止快速切换时旧请求覆盖新数据 - FoodListFragment:缓存 currentFoodName,返回二级页面后 onResume 复用搜索词 - CookingModeActivity:RadioGroup+RadioButton 替换为 LinearLayout+CheckedTextView - CookingModeActivity:新增 selectTab() 统一管理选中态,内置幂等保护 - CookingModeActivity:重构 onNewIntent,消除切换餐次时 getDishList 被双重调用的问题 - VectorCollectionFragment:Error/Success 消费后重置 StateFlow,防止 replay 导致 toast 重复弹出 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -97,17 +97,9 @@ class CookingModeActivity : BaseActivity() {
|
||||
private var isClearingByTabSwitch = false
|
||||
|
||||
private fun addViewListener() {
|
||||
binding.dishRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
dinnerType = when (checkedId) {
|
||||
R.id.rbBreakfast -> "1"
|
||||
R.id.rbLunch -> "2"
|
||||
R.id.rbDinner -> "3"
|
||||
else -> "1"
|
||||
}
|
||||
// 切换餐次时清空搜索框,避免用户误以为列表是被过滤的状态
|
||||
clearSearchText()
|
||||
loadFragment()
|
||||
}
|
||||
binding.tvBreakfast.setOnClickListener { selectTab("1") }
|
||||
binding.tvLunch.setOnClickListener { selectTab("2") }
|
||||
binding.tvDinner.setOnClickListener { selectTab("3") }
|
||||
binding.etInputDish.addOnActionSearchListener {
|
||||
jumpSearch()
|
||||
}
|
||||
@@ -122,6 +114,20 @@ class CookingModeActivity : BaseActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换餐次 Tab:更新选中态、清空搜索框、切换 Fragment
|
||||
* 由点击事件调用,不触发额外的 getDishList(),由 onHiddenChanged 负责刷新
|
||||
*/
|
||||
private fun selectTab(type: String) {
|
||||
if (dinnerType == type) return
|
||||
dinnerType = type
|
||||
binding.tvBreakfast.isChecked = type == "1"
|
||||
binding.tvLunch.isChecked = type == "2"
|
||||
binding.tvDinner.isChecked = type == "3"
|
||||
clearSearchText()
|
||||
loadFragment()
|
||||
}
|
||||
|
||||
/** 获取当前显示的 FoodListFragment */
|
||||
private fun currentFragment(): FoodListFragment? {
|
||||
return runCatching { fragmentList[dinnerType.toInt() - 1] }.getOrNull()
|
||||
@@ -169,16 +175,15 @@ class CookingModeActivity : BaseActivity() {
|
||||
if (isConfigPage) {
|
||||
return
|
||||
}
|
||||
//val isCooking = intent.getBooleanExtra(IS_COOKING, false)
|
||||
val lastDinnerType = intent.getStringExtra(DINNER_TYPE) ?: "0"
|
||||
if (lastDinnerType == "1" || lastDinnerType == "2" || lastDinnerType == "3") {
|
||||
if (lastDinnerType !in listOf("1", "2", "3")) return
|
||||
if (lastDinnerType == dinnerType) {
|
||||
// 同一 Tab:onHiddenChanged 不会触发,直接刷新
|
||||
clearSearchText()
|
||||
fragmentList.getOrNull(lastDinnerType.toInt() - 1)?.getDishList(1, lastDinnerType)
|
||||
}
|
||||
when (lastDinnerType) {
|
||||
"1" -> binding.dishRadioGroup.check(R.id.rbBreakfast)
|
||||
"2" -> binding.dishRadioGroup.check(R.id.rbLunch)
|
||||
"3" -> binding.dishRadioGroup.check(R.id.rbDinner)
|
||||
currentFragment()?.getDishList(1, lastDinnerType)
|
||||
} else {
|
||||
// 不同 Tab:selectTab → loadFragment → show → onHiddenChanged 负责刷新,无需直接调用
|
||||
selectTab(lastDinnerType)
|
||||
}
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.yanzhenjie.recyclerview.SwipeMenuItem
|
||||
import androidx.fragment.app.viewModels
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.io.Serializable
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -55,6 +56,12 @@ class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
|
||||
|
||||
private var dinnerType = "0"
|
||||
|
||||
/** 跟踪当前请求协程,新请求发起前取消旧的,防止并发竞态导致数据错乱 */
|
||||
private var currentJob: Job? = null
|
||||
|
||||
/** 缓存最后一次搜索词,返回二级页面后 onResume 可复用,避免搜索态被重置为全量查询 */
|
||||
private var currentFoodName: String? = null
|
||||
|
||||
private lateinit var activity: CookingModeActivity
|
||||
|
||||
/** 第一页时先查数据库存入此字段,网络结果回来后直接使用,避免时序问题 */
|
||||
@@ -140,7 +147,7 @@ class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
|
||||
super.onResume()
|
||||
runCatching {
|
||||
pageNo = 1
|
||||
getDishList()
|
||||
getDishList(currentFoodName)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
@@ -200,7 +207,9 @@ class FoodListFragment : BaseFragment<FragmentFoodListBinding>() {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Suppress("unchecked_cast")
|
||||
fun getDishList(foodName: String? = null) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
currentFoodName = foodName
|
||||
currentJob?.cancel()
|
||||
currentJob = viewLifecycleOwner.lifecycleScope.launch {
|
||||
val param = mutableMapOf<String, Any>(
|
||||
"pageNum" to pageNo,
|
||||
"pageSize" to pageSize,
|
||||
|
||||
@@ -6,59 +6,62 @@
|
||||
android:orientation="vertical"
|
||||
tools:background="@drawable/bg_other_page">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/dishRadioGroup"
|
||||
<LinearLayout
|
||||
android:id="@+id/llDishTabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="88dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbBreakfast"
|
||||
<CheckedTextView
|
||||
android:id="@+id/tvBreakfast"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/breakfast_bg"
|
||||
android:button="@null"
|
||||
android:checkMark="@null"
|
||||
android:checked="true"
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/text_breakfast"
|
||||
android:textColor="@color/breakfast_font"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbLunch"
|
||||
<CheckedTextView
|
||||
android:id="@+id/tvLunch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="9dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/breakfast_bg"
|
||||
android:button="@null"
|
||||
android:checkMark="@null"
|
||||
android:checked="false"
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/text_lunch"
|
||||
android:textColor="@color/breakfast_font"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbDinner"
|
||||
<CheckedTextView
|
||||
android:id="@+id/tvDinner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="9dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/breakfast_bg"
|
||||
android:button="@null"
|
||||
android:checkMark="@null"
|
||||
android:checked="false"
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/text_dinner"
|
||||
android:textColor="@color/breakfast_font"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
</RadioGroup>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llSearchBar"
|
||||
|
||||
Reference in New Issue
Block a user