如何在 Android 中仅使用 MutableLiveData 更新已更改的项目?
How to update only changed item with MutableLiveData in Android?
我用 ViewPager2 和 Tabs 嵌套了片段,我正在用 MutableLiveData
将数据加载到 RecyclerView
。一切正常,直到我更新我的 Firebase 实时数据库上的某些内容(例如,某些食品的名称)。因此,如果我有 10 个类别项目,每个项目有 5 种食品,并且我更新了 1 种食品的名称,我的屏幕会闪烁,并且添加了 10 个新类别,每个类别有 5 种食品,现在我 总共有 20 个类别..
期望的行为 将是:更新数据,无屏幕闪烁,仅更新更改的项目而无需重新添加所有类别和食物列表
那么我怎样才能让我的 MutableLiveData 更新刚刚更改的项目,而不是整个列表?
ViewModel
public class MenuViewModel extends ViewModel implements
ICategoryCallbackListener, IFoodCallbackListener {
private MutableLiveData<String> messageError = new MutableLiveData<>();
private MutableLiveData<List<CategoryModel>> categoryListMutable;
private ICategoryCallbackListener categoryCallbackListener;
private MutableLiveData<List<FoodModel>> foodListMutable;
private IFoodCallbackListener foodCallbackListener;
public MenuViewModel() {
categoryCallbackListener = this;
foodCallbackListener = this;
}
public MutableLiveData<List<CategoryModel>> getCategoryListMutable() {
if(categoryListMutable == null)
{
categoryListMutable = new MutableLiveData<>();
messageError = new MutableLiveData<>();
loadCategories();
}
return categoryListMutable;
}
public MutableLiveData<List<FoodModel>> getFoodListMutable(String key) {
if(foodListMutable == null)
{
foodListMutable = new MutableLiveData<>();
messageError = new MutableLiveData<>();
loadFood(key);
}
return foodListMutable;
}
public void loadCategories() {
List<CategoryModel> tempList = new ArrayList<>();
DatabaseReference categoryRef = FirebaseDatabase.getInstance()
.getReference(Common.RESTAURANT_REF)
.child(Common.currentRestaurant.getUid())
.child(Common.CATEGORY_REF);
categoryRef.keepSynced(true);
categoryRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
for(DataSnapshot itemSnapShot: snapshot.getChildren())
{
CategoryModel categoryModel=itemSnapShot.getValue(CategoryModel.class);
if(categoryModel != null)
categoryModel.setMenu_id(itemSnapShot.getKey());
tempList.add(categoryModel);
}
categoryCallbackListener.onCategoryLoadSuccess(tempList);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
categoryCallbackListener.onCategoryLoadFailed(error.getMessage());
}
});
}
public void loadFood(String key) {
List<FoodModel> tempList = new ArrayList<>();
DatabaseReference foodRef = FirebaseDatabase.getInstance()
.getReference(Common.RESTAURANT_REF)
.child(Common.currentRestaurant.getUid())
.child(Common.CATEGORY_REF)
.child(key)
.child(Common.FOOD_REF);
foodRef.keepSynced(true);
foodRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
for(DataSnapshot itemSnapShot: snapshot.getChildren())
{
FoodModel foodModel = itemSnapShot.getValue(FoodModel.class);
tempList.add(foodModel);
}
foodCallbackListener.onFoodLoadSuccess(tempList);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
foodCallbackListener.onFoodLoadFailed(error.getMessage());
}
});
}
public MutableLiveData<String> getMessageError() {
return messageError;
}
@Override
public void onCategoryLoadSuccess(List<CategoryModel> categoryModels) {
categoryListMutable.setValue(categoryModels);
}
@Override
public void onCategoryLoadFailed(String message) {
messageError.setValue(message);
}
@Override
public void onFoodLoadSuccess(List<FoodModel> foodModels) {
foodListMutable.setValue(foodModels);
}
@Override
public void onFoodLoadFailed(String message) {
messageError.setValue(message);
}
菜单片段
public class MenuFragment extends Fragment {
public static final String ARG_MENU = "menu";
private MenuViewModel menuViewModel;
//Irrelevant code
MyFoodListAdapter adapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
menuViewModel = new ViewModelProvider(this).get(MenuViewModel.class);
View root = inflater.inflate(R.layout.fragment_menu, container, false);
//Irrelevant code
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
menuViewModel.getFoodListMutable(Objects.requireNonNull(args)
.getString(ARG_MENU))
.observe(getViewLifecycleOwner(), foodModels -> {
adapter = new MyFoodListAdapter(getContext(), foodModels);
recycler_menu.setAdapter(adapter);
});
}
}
类别模型
public class CategoryModel {
private String menu_id, name, image, background;
private Long numberOfOrders;
List<FoodModel> foods;//Setters and Getters}
如果您将 ValueEventListener
附加到某个位置,每次在该位置下修改任何内容时,您都会收到该位置所有数据的快照。
只要发生这种情况,您的 onDataChange
就会将快照中的项目添加到 tempList
。所以在初始加载时它添加了 10 个类别。然后当有变化时,它会再次添加它们,最后你会得到 20 个类别。
删除重复项目的最简单方法是在添加项目之前清除列表:
categoryRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
tempList.clear();
for(DataSnapshot itemSnapShot: snapshot.getChildren())
{
CategoryModel categoryModel=itemSnapShot.getValue(CategoryModel.class);
if(categoryModel != null)
categoryModel.setMenu_id(itemSnapShot.getKey());
tempList.add(categoryModel);
}
categoryCallbackListener.onCategoryLoadSuccess(tempList);
}
这消除了重复项,但在您强制 Android 重新绘制整个列表时,可能仍会导致一些闪烁。如果您也想摆脱它,请考虑使用 addChildEventListener
。使用这种类型的侦听器,您会收到有关单个子节点更改的通知,并可以使用该信息对 tempList
执行最小更新,然后您还可以通过调用 Android 来执行此操作 notifyItemChanged
and similar methods. This is pretty much what the adapters in FirebaseUI做。
我用 ViewPager2 和 Tabs 嵌套了片段,我正在用 MutableLiveData
将数据加载到 RecyclerView
。一切正常,直到我更新我的 Firebase 实时数据库上的某些内容(例如,某些食品的名称)。因此,如果我有 10 个类别项目,每个项目有 5 种食品,并且我更新了 1 种食品的名称,我的屏幕会闪烁,并且添加了 10 个新类别,每个类别有 5 种食品,现在我 总共有 20 个类别..
期望的行为 将是:更新数据,无屏幕闪烁,仅更新更改的项目而无需重新添加所有类别和食物列表
那么我怎样才能让我的 MutableLiveData 更新刚刚更改的项目,而不是整个列表?
ViewModel
public class MenuViewModel extends ViewModel implements
ICategoryCallbackListener, IFoodCallbackListener {
private MutableLiveData<String> messageError = new MutableLiveData<>();
private MutableLiveData<List<CategoryModel>> categoryListMutable;
private ICategoryCallbackListener categoryCallbackListener;
private MutableLiveData<List<FoodModel>> foodListMutable;
private IFoodCallbackListener foodCallbackListener;
public MenuViewModel() {
categoryCallbackListener = this;
foodCallbackListener = this;
}
public MutableLiveData<List<CategoryModel>> getCategoryListMutable() {
if(categoryListMutable == null)
{
categoryListMutable = new MutableLiveData<>();
messageError = new MutableLiveData<>();
loadCategories();
}
return categoryListMutable;
}
public MutableLiveData<List<FoodModel>> getFoodListMutable(String key) {
if(foodListMutable == null)
{
foodListMutable = new MutableLiveData<>();
messageError = new MutableLiveData<>();
loadFood(key);
}
return foodListMutable;
}
public void loadCategories() {
List<CategoryModel> tempList = new ArrayList<>();
DatabaseReference categoryRef = FirebaseDatabase.getInstance()
.getReference(Common.RESTAURANT_REF)
.child(Common.currentRestaurant.getUid())
.child(Common.CATEGORY_REF);
categoryRef.keepSynced(true);
categoryRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
for(DataSnapshot itemSnapShot: snapshot.getChildren())
{
CategoryModel categoryModel=itemSnapShot.getValue(CategoryModel.class);
if(categoryModel != null)
categoryModel.setMenu_id(itemSnapShot.getKey());
tempList.add(categoryModel);
}
categoryCallbackListener.onCategoryLoadSuccess(tempList);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
categoryCallbackListener.onCategoryLoadFailed(error.getMessage());
}
});
}
public void loadFood(String key) {
List<FoodModel> tempList = new ArrayList<>();
DatabaseReference foodRef = FirebaseDatabase.getInstance()
.getReference(Common.RESTAURANT_REF)
.child(Common.currentRestaurant.getUid())
.child(Common.CATEGORY_REF)
.child(key)
.child(Common.FOOD_REF);
foodRef.keepSynced(true);
foodRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
for(DataSnapshot itemSnapShot: snapshot.getChildren())
{
FoodModel foodModel = itemSnapShot.getValue(FoodModel.class);
tempList.add(foodModel);
}
foodCallbackListener.onFoodLoadSuccess(tempList);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
foodCallbackListener.onFoodLoadFailed(error.getMessage());
}
});
}
public MutableLiveData<String> getMessageError() {
return messageError;
}
@Override
public void onCategoryLoadSuccess(List<CategoryModel> categoryModels) {
categoryListMutable.setValue(categoryModels);
}
@Override
public void onCategoryLoadFailed(String message) {
messageError.setValue(message);
}
@Override
public void onFoodLoadSuccess(List<FoodModel> foodModels) {
foodListMutable.setValue(foodModels);
}
@Override
public void onFoodLoadFailed(String message) {
messageError.setValue(message);
}
菜单片段
public class MenuFragment extends Fragment {
public static final String ARG_MENU = "menu";
private MenuViewModel menuViewModel;
//Irrelevant code
MyFoodListAdapter adapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
menuViewModel = new ViewModelProvider(this).get(MenuViewModel.class);
View root = inflater.inflate(R.layout.fragment_menu, container, false);
//Irrelevant code
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Bundle args = getArguments();
menuViewModel.getFoodListMutable(Objects.requireNonNull(args)
.getString(ARG_MENU))
.observe(getViewLifecycleOwner(), foodModels -> {
adapter = new MyFoodListAdapter(getContext(), foodModels);
recycler_menu.setAdapter(adapter);
});
}
}
类别模型
public class CategoryModel {
private String menu_id, name, image, background;
private Long numberOfOrders;
List<FoodModel> foods;//Setters and Getters}
如果您将 ValueEventListener
附加到某个位置,每次在该位置下修改任何内容时,您都会收到该位置所有数据的快照。
只要发生这种情况,您的 onDataChange
就会将快照中的项目添加到 tempList
。所以在初始加载时它添加了 10 个类别。然后当有变化时,它会再次添加它们,最后你会得到 20 个类别。
删除重复项目的最简单方法是在添加项目之前清除列表:
categoryRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
tempList.clear();
for(DataSnapshot itemSnapShot: snapshot.getChildren())
{
CategoryModel categoryModel=itemSnapShot.getValue(CategoryModel.class);
if(categoryModel != null)
categoryModel.setMenu_id(itemSnapShot.getKey());
tempList.add(categoryModel);
}
categoryCallbackListener.onCategoryLoadSuccess(tempList);
}
这消除了重复项,但在您强制 Android 重新绘制整个列表时,可能仍会导致一些闪烁。如果您也想摆脱它,请考虑使用 addChildEventListener
。使用这种类型的侦听器,您会收到有关单个子节点更改的通知,并可以使用该信息对 tempList
执行最小更新,然后您还可以通过调用 Android 来执行此操作 notifyItemChanged
and similar methods. This is pretty much what the adapters in FirebaseUI做。