MutableLiveData 未发布正确的值 - Android
MutableLiveData is not posting the correct value - Android
我正在围绕膳食构建一个应用程序 API。首先将显示类别列表。选择类别后,将显示特定类别的餐点。用户可以将任何餐点添加到收藏夹。用户可以通过单击顶部的收藏夹图标来查看收藏夹列表。如果用户点击一顿饭,他就可以看到这顿饭菜谱的详细信息。我正在使用相同的片段和视图模型来显示基于类别的餐点以及最喜欢的餐点。我将 MVVM 与 MutableLiveData 一起使用。收藏夹图标在 activity 中,我继续替换 activity 中的片段。
如果我单击 CategoriesFragment 或 FilterByTypeFragment 中的收藏夹图标,所有添加到收藏夹的食物都会正确显示。但是,如果我在 RecipeDetailsFragment 中单击收藏夹图标,它只会显示该类别的所有餐点。这意味着,它就像在 RecipeDetailsFragment 上按回键一样工作。当我调试时,我看到当我单击收藏夹图标时在视图模型中调用了 fetchFavorites 方法。但是观察者最后得到的是所选类别的所有餐点的列表。
当我从 RecipeDetailsFragment 导航到收藏夹部分时,为什么我看不到收藏夹列表?
我发布下面的代码以供您理解:
@HiltViewModel
class FilterByCategoryViewModel @Inject constructor(
val dataManager: AppDataManager,
val networkHelper: NetworkHelper,
val category: String?,
val isFavorites: String = "N"
) : ViewModel() {
val _meals = MutableLiveData<Resource<MealsResponse>>()
init {
if (isFavorites.equals("Y")) fetchFavorites()
else fetchMealsByCategory(category)
}
fun fetchMealsByCategory(category: String?) {
viewModelScope.launch {
_meals.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
launch(Dispatchers.IO) {
dataManager.getMealsByCategory(category!!).let {
if (it.isSuccessful) {
_meals.postValue(Resource.success(it.body()))
} else _meals.postValue(
Resource.error(
it.errorBody().toString(),
null
)
)
}
}
} else _meals.postValue(Resource.error("No Internet Connection", null))
}
}
fun fetchFavorites() {
viewModelScope.launch {
_meals.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
launch(Dispatchers.IO) {
dataManager.getFavoriteMeals().let {
if (it.isSuccessful) {
println("Body: " + it.body().toString())
_meals.postValue(Resource.success(it.body()))
println(_meals.getValue().toString())
} else _meals.postValue(
Resource.error(
it.errorBody().toString(),
null
)
)
}
}
} else _meals.postValue(Resource.error("No Internet Connection", null))
}
}
fun onFavoriteClicked(meal: Meal) {
viewModelScope.launch {
val job = launch(Dispatchers.IO) {
val isFavorite = dataManager.isFavorite(meal)
val _meal = meal.copy(
isFavorite = when (isFavorite) {
1 -> 0
else -> 1
}
)
dataManager.setFavorite(_meal)
}
job.join()
if (isFavorites.equals("Y"))
fetchFavorites()
else
fetchMealsByCategory(category)
}
}
}
片段:
@AndroidEntryPoint
class FilterByTypeFragment : Fragment(), MealAdapter.FavoriteClickListener {
@Inject
lateinit var dataManager: AppDataManager
@Inject
lateinit var networkHelper: NetworkHelper
private lateinit var adapter: MealAdapter
private lateinit var binding: FragmentCategoriesBinding
private var category: String? = null
private var isFavorites: String = "N"
lateinit private var filterByCategoryViewModel: FilterByCategoryViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<FragmentCategoriesBinding>(
inflater,
R.layout.fragment_categories, container, false
)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val controller: NavController = Navigation.findNavController(view!!)
controller.popBackStack(R.id.recipeDetailFragment, true)
if (arguments != null) {
category = FilterByTypeFragmentArgs.fromBundle(requireArguments()).category
isFavorites = FilterByTypeFragmentArgs.fromBundle(requireArguments()).isFavorites
}
setupUI()
}
private fun setupUI() {
binding.recyclerView.layoutManager = GridLayoutManager(activity, 2)
adapter = MealAdapter(arrayListOf(), this)
binding.recyclerView.addItemDecoration(
GridSpacingItemDecoration(true, 2, 20, true)
)
binding.recyclerView.adapter = adapter
}
override fun onResume() {
super.onResume()
val factory = FilterByTypeViewModelFactory(dataManager, networkHelper, category, isFavorites)
filterByCategoryViewModel = ViewModelProvider(
this,
factory
).get(FilterByCategoryViewModel::class.java)
setupObserver()
}
private fun setupObserver() {
filterByCategoryViewModel._meals.observe(this, Observer {
when (it.status) {
Status.SUCCESS -> {
binding.progressBar.visibility = View.GONE
it.data?.let { users ->
renderList(users.meals)
}
binding.recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
binding.progressBar.visibility = View.VISIBLE
binding.recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
binding.progressBar.visibility = View.GONE
Toast.makeText(activity, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(meals: List<Meal>) {
println("Meals: " + meals.toString())
adapter.clearData()
adapter.addData(meals)
adapter.notifyDataSetChanged()
}
override fun onFavoriteClick(meal: Meal) {
filterByCategoryViewModel.onFavoriteClicked(meal)
}
}
以正确答案的形式在评论中写下讨论:
事实证明,问题是您正在导航到一个已经存在于后台堆栈中的目的地,并且由于有已经有一个作用域为该片段的 ViewModel,您收到了相同的 viewModel 实例(它的构造函数中已经有一个 isFavorites
字段。
通过在导航之前从后退堆栈中删除旧的目的地来解决问题,以便我们获得一个新的 ViewModel 实例。
除此之外,我建议不要将此类字段(依赖于业务逻辑)作为视图模型的依赖项(在您的情况下 category
和 isFavorites
)。相反,您可以采用其他一些方法,例如:
- 将
category
和 isFavorites
作为导航参数传递,并使用 SavedStateHandle
或 在 ViewModel 中检索它们
- 在您的 ViewModel 中将这些字段表示为 LiveData 或 Flow,并对这些值的变化做出反应。
我正在围绕膳食构建一个应用程序 API。首先将显示类别列表。选择类别后,将显示特定类别的餐点。用户可以将任何餐点添加到收藏夹。用户可以通过单击顶部的收藏夹图标来查看收藏夹列表。如果用户点击一顿饭,他就可以看到这顿饭菜谱的详细信息。我正在使用相同的片段和视图模型来显示基于类别的餐点以及最喜欢的餐点。我将 MVVM 与 MutableLiveData 一起使用。收藏夹图标在 activity 中,我继续替换 activity 中的片段。
如果我单击 CategoriesFragment 或 FilterByTypeFragment 中的收藏夹图标,所有添加到收藏夹的食物都会正确显示。但是,如果我在 RecipeDetailsFragment 中单击收藏夹图标,它只会显示该类别的所有餐点。这意味着,它就像在 RecipeDetailsFragment 上按回键一样工作。当我调试时,我看到当我单击收藏夹图标时在视图模型中调用了 fetchFavorites 方法。但是观察者最后得到的是所选类别的所有餐点的列表。 当我从 RecipeDetailsFragment 导航到收藏夹部分时,为什么我看不到收藏夹列表? 我发布下面的代码以供您理解:
@HiltViewModel
class FilterByCategoryViewModel @Inject constructor(
val dataManager: AppDataManager,
val networkHelper: NetworkHelper,
val category: String?,
val isFavorites: String = "N"
) : ViewModel() {
val _meals = MutableLiveData<Resource<MealsResponse>>()
init {
if (isFavorites.equals("Y")) fetchFavorites()
else fetchMealsByCategory(category)
}
fun fetchMealsByCategory(category: String?) {
viewModelScope.launch {
_meals.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
launch(Dispatchers.IO) {
dataManager.getMealsByCategory(category!!).let {
if (it.isSuccessful) {
_meals.postValue(Resource.success(it.body()))
} else _meals.postValue(
Resource.error(
it.errorBody().toString(),
null
)
)
}
}
} else _meals.postValue(Resource.error("No Internet Connection", null))
}
}
fun fetchFavorites() {
viewModelScope.launch {
_meals.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
launch(Dispatchers.IO) {
dataManager.getFavoriteMeals().let {
if (it.isSuccessful) {
println("Body: " + it.body().toString())
_meals.postValue(Resource.success(it.body()))
println(_meals.getValue().toString())
} else _meals.postValue(
Resource.error(
it.errorBody().toString(),
null
)
)
}
}
} else _meals.postValue(Resource.error("No Internet Connection", null))
}
}
fun onFavoriteClicked(meal: Meal) {
viewModelScope.launch {
val job = launch(Dispatchers.IO) {
val isFavorite = dataManager.isFavorite(meal)
val _meal = meal.copy(
isFavorite = when (isFavorite) {
1 -> 0
else -> 1
}
)
dataManager.setFavorite(_meal)
}
job.join()
if (isFavorites.equals("Y"))
fetchFavorites()
else
fetchMealsByCategory(category)
}
}
}
片段:
@AndroidEntryPoint
class FilterByTypeFragment : Fragment(), MealAdapter.FavoriteClickListener {
@Inject
lateinit var dataManager: AppDataManager
@Inject
lateinit var networkHelper: NetworkHelper
private lateinit var adapter: MealAdapter
private lateinit var binding: FragmentCategoriesBinding
private var category: String? = null
private var isFavorites: String = "N"
lateinit private var filterByCategoryViewModel: FilterByCategoryViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<FragmentCategoriesBinding>(
inflater,
R.layout.fragment_categories, container, false
)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val controller: NavController = Navigation.findNavController(view!!)
controller.popBackStack(R.id.recipeDetailFragment, true)
if (arguments != null) {
category = FilterByTypeFragmentArgs.fromBundle(requireArguments()).category
isFavorites = FilterByTypeFragmentArgs.fromBundle(requireArguments()).isFavorites
}
setupUI()
}
private fun setupUI() {
binding.recyclerView.layoutManager = GridLayoutManager(activity, 2)
adapter = MealAdapter(arrayListOf(), this)
binding.recyclerView.addItemDecoration(
GridSpacingItemDecoration(true, 2, 20, true)
)
binding.recyclerView.adapter = adapter
}
override fun onResume() {
super.onResume()
val factory = FilterByTypeViewModelFactory(dataManager, networkHelper, category, isFavorites)
filterByCategoryViewModel = ViewModelProvider(
this,
factory
).get(FilterByCategoryViewModel::class.java)
setupObserver()
}
private fun setupObserver() {
filterByCategoryViewModel._meals.observe(this, Observer {
when (it.status) {
Status.SUCCESS -> {
binding.progressBar.visibility = View.GONE
it.data?.let { users ->
renderList(users.meals)
}
binding.recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
binding.progressBar.visibility = View.VISIBLE
binding.recyclerView.visibility = View.GONE
}
Status.ERROR -> {
//Handle Error
binding.progressBar.visibility = View.GONE
Toast.makeText(activity, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(meals: List<Meal>) {
println("Meals: " + meals.toString())
adapter.clearData()
adapter.addData(meals)
adapter.notifyDataSetChanged()
}
override fun onFavoriteClick(meal: Meal) {
filterByCategoryViewModel.onFavoriteClicked(meal)
}
}
以正确答案的形式在评论中写下讨论:
事实证明,问题是您正在导航到一个已经存在于后台堆栈中的目的地,并且由于有已经有一个作用域为该片段的 ViewModel,您收到了相同的 viewModel 实例(它的构造函数中已经有一个 isFavorites
字段。
通过在导航之前从后退堆栈中删除旧的目的地来解决问题,以便我们获得一个新的 ViewModel 实例。
除此之外,我建议不要将此类字段(依赖于业务逻辑)作为视图模型的依赖项(在您的情况下 category
和 isFavorites
)。相反,您可以采用其他一些方法,例如:
- 将
category
和isFavorites
作为导航参数传递,并使用SavedStateHandle
或 在 ViewModel 中检索它们
- 在您的 ViewModel 中将这些字段表示为 LiveData 或 Flow,并对这些值的变化做出反应。