如何在Android中使用MutableLiveData仅更新已更改的项?

huangapple go评论78阅读模式
英文:

How to update only changed item with MutableLiveData in Android?

问题

以下是您要求的翻译内容:

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);
    }
}

MenuFragment

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);
                });
    }
}

CategoryModel

public class CategoryModel {
    private String menu_id, name, image, background;
    private Long numberOfOrders;
    List<FoodModel> foods; //Setters and Getters
}
英文:

I have nested fragments with ViewPager2 and Tabs, and I'm loading data into RecyclerView with MutableLiveData. Everything works fine till I update something on my Firebase Realtime Database (eg. name of some food item). So if I have 10 category items with each having 5 food items, and I update name of 1 food, my screen flickers and 10 new categories are added with each having 5 food items and now I have total 20 categories..

Desired behaviour would be: Update data, no screen flickers, just updating changed item WITHOUT adding all that categories and food lists all over again

So how could I achieve that my MutableLiveData would update just changed item, not whole list?

ViewModel

public class MenuViewModel extends ViewModel implements 
ICategoryCallbackListener, IFoodCallbackListener {
private MutableLiveData&lt;String&gt; messageError = new MutableLiveData&lt;&gt;();
private MutableLiveData&lt;List&lt;CategoryModel&gt;&gt; categoryListMutable;
private ICategoryCallbackListener categoryCallbackListener;
private MutableLiveData&lt;List&lt;FoodModel&gt;&gt; foodListMutable;
private IFoodCallbackListener foodCallbackListener;
public MenuViewModel() {
categoryCallbackListener = this;
foodCallbackListener = this;
}
public MutableLiveData&lt;List&lt;CategoryModel&gt;&gt; getCategoryListMutable() {
if(categoryListMutable == null)
{
categoryListMutable = new MutableLiveData&lt;&gt;();
messageError = new MutableLiveData&lt;&gt;();
loadCategories();
}
return categoryListMutable;
}
public MutableLiveData&lt;List&lt;FoodModel&gt;&gt; getFoodListMutable(String key) {
if(foodListMutable == null)
{
foodListMutable = new MutableLiveData&lt;&gt;();
messageError = new MutableLiveData&lt;&gt;();
loadFood(key);
}
return foodListMutable;
}
public void loadCategories() {
List&lt;CategoryModel&gt; tempList = new ArrayList&lt;&gt;();
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&lt;FoodModel&gt; tempList = new ArrayList&lt;&gt;();
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&lt;String&gt; getMessageError() {
return messageError;
}
@Override
public void onCategoryLoadSuccess(List&lt;CategoryModel&gt; categoryModels) {
categoryListMutable.setValue(categoryModels);
}
@Override
public void onCategoryLoadFailed(String message) {
messageError.setValue(message);
}
@Override
public void onFoodLoadSuccess(List&lt;FoodModel&gt; foodModels) {
foodListMutable.setValue(foodModels);
}
@Override
public void onFoodLoadFailed(String message) {
messageError.setValue(message);
}

MenuFragment

    public class MenuFragment extends Fragment {
public static final String ARG_MENU = &quot;menu&quot;;
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 -&gt; {
adapter = new MyFoodListAdapter(getContext(), foodModels);
recycler_menu.setAdapter(adapter);
});
}
}

CategoryModel

public class CategoryModel {
private String menu_id, name, image, background;
private Long numberOfOrders;
List&lt;FoodModel&gt; foods;//Setters and Getters}

答案1

得分: 0

如果您将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执行最小更新,然后还可以通过调用notifyItemChanged等方法告诉Android执行更新。这基本上是FirebaseUI中的适配器所做的事情。

英文:

If you attach a ValueEventListener to a location, you get called with a snapshot of all data at that location each time anything is modified under it.

Your onDataChange adds the items in the snapshot to tempList whenever that happens. So on the initial load it adds the 10 categories. Then when there's a change, it adds them again and you end up with 20 categories.

The simplest way to get rid of the duplicate items, is to clear the list before adding the items to it:

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);
}

This gets rid of the duplicates, but will probably still result in some flicker as you're forcing Android to repaint the entire list. If you also want to get rid of that, consider using addChildEventListener. With that type of listener you get notified of the changes to the individual child node, and can use that information to perform a minimal update to tempList, which you can then also tell Android to perform by calling notifyItemChanged and similar methods. This is pretty much what the adapters in FirebaseUI do.

huangapple
  • 本文由 发表于 2020年9月24日 18:52:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/64044924.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定