简体   繁体   English

Android 对记录数据进行更新时不会触发片段 LiveData 观察器

[英]Android Fragment LiveData observer is not triggered when update is done on a record data

I am trying to figure out why the LiveData observer for getAllGoals() does not trigger immediately in the fragment when I update a record.我试图弄清楚为什么当我更新记录时getAllGoals()的 LiveData 观察者不会立即在片段中触发。 However, the observer is called only after switching to another fragment using the bottom tab navigation and then coming back to the original fragment.但是,只有在使用底部选项卡导航切换到另一个片段然后返回到原始片段后,才会调用观察者。

The fragment in question: MyGoalsFragment.java有问题的片段:MyGoalsFragment.java


public class MyGoalsFragment extends Fragment implements MyGoalsAdapter.MyGoalsCallback {

    FragmentMyGoalsBinding myGoalsBinding;
    private MyGoalsViewModel myGoalsViewModel;
    MyGoalsAdapter myGoalsAdapter;
    ConstraintSet smallConstraintSet = new ConstraintSet();

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) { 
                             
        myGoalsViewModel = new ViewModelProvider(getActivity(), ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication())).get(MyGoalsViewModel.class);
        myGoalsBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_my_goals, container, false);

        myGoalsBinding.recyclerView2.setLayoutManager(new LinearLayoutManager(getActivity()));
        DrawerLayout drawerLayout = (DrawerLayout) getActivity().findViewById(R.id.drawer_layout);
        myGoalsBinding.menu.setOnClickListener(v -> {
            drawerLayout.openDrawer(GravityCompat.START);
        });

        TransitionManager.beginDelayedTransition(myGoalsBinding.recyclerView2);
        myGoalsAdapter = new MyGoalsAdapter();
        myGoalsAdapter.setCallback(this);
        myGoalsAdapter.setContext(getActivity());
        myGoalsAdapter.setRecyclerView(myGoalsBinding.recyclerView2);

        myGoalsBinding.recyclerView2.setAdapter(myGoalsAdapter);


        myGoalsBinding.floatingActionButton.setOnClickListener(v -> {
            startActivity(new Intent(getActivity(), CreateGoalActivity.class));
            getActivity().finish();

        });

        enableSwipeToDeleteAndUndo();

        myGoalsBinding.recyclerView2.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (dy > 0 && myGoalsBinding.floatingActionButton.getVisibility() == View.VISIBLE) {
                    myGoalsBinding.floatingActionButton.hide();
                } else if (dy < 0 && myGoalsBinding.floatingActionButton.getVisibility() != View.VISIBLE) {
                    myGoalsBinding.floatingActionButton.show();
                }
            }
        });

        myGoalsViewModel.getAllGoals().observe(getViewLifecycleOwner(), new Observer<List<Goal>>() {
            @Override
            public void onChanged(List<Goal> goals) {
                myGoalsAdapter.submitList(goals); // This observer is not called even after updating a record
            }
        });

        return myGoalsBinding.getRoot();
    }

    @Override
    public void editGoalCallback(Goal goal) {
        Intent intent = new Intent(getActivity(), CreateGoalActivity.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("goal", goal);
        intent.putExtras(bundle);
        startActivity(intent);
    }

    @Override
    public void goalCheckBoxCallback(Goal goal) {
        myGoalsViewModel.updateGoal(goal); 
    }

    private void enableSwipeToDeleteAndUndo() {
        SwipeToDeleteCallback swipeToDeleteCallback = new SwipeToDeleteCallback(getActivity()) {
            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {


                if(i==ItemTouchHelper.LEFT) {
                    Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition());
                    myGoalsViewModel.deleteGoal(tempGoal);
                    Snackbar.make(myGoalsBinding.rootConstraintLayout, "Goal Deleted", Snackbar.LENGTH_LONG)
                            .setAction("Undo", v -> {
                                myGoalsViewModel.insertGoal(tempGoal);
                            })
                            .setActionTextColor(getActivity().getResources().getColor(R.color.arcticLimeGreen))
                            .show();

                }else if(i==ItemTouchHelper.RIGHT){
                    Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition());

                    if(tempGoal.isCompleted())
                        tempGoal.setCompleted(false);
                    else
                        tempGoal.setCompleted(true);

                    TransitionManager.beginDelayedTransition(myGoalsBinding.recyclerView2);
                    
                    myGoalsViewModel.updateGoal(tempGoal); // This is where the update is called
                }
            }
        };

        ItemTouchHelper itemTouchhelper = new ItemTouchHelper(swipeToDeleteCallback);
        itemTouchhelper.attachToRecyclerView(myGoalsBinding.recyclerView2);
    }

}


The MyGoals ViewModel: MyGoals 视图模型:


public class MyGoalsViewModel extends AndroidViewModel {

    private NoteRepository repository;
    private LiveData<List<Goal>> allGoals;


    public MyGoalsViewModel(@NonNull Application application) {
        super(application);
        repository = new NoteRepository(application);
        allGoals = repository.getAllGoals();

    }

    public LiveData<List<Goal>> getAllGoals(){
        return allGoals;
    }

    public void deleteGoal(Goal goal){repository.deleteGoal(goal);}

    public void insertGoal(Goal goal){repository.insertGoal(goal);}

    public void updateGoal(Goal goal){repository.updateGoal(goal);}

}

The Repository:存储库:

public class NoteRepository {

    private String DB_NAME = "db_task";

    Context context;
    private GoalDao goalDao;
    private LiveData<List<Goal>> allGoals;

    private NoteDatabase noteDatabase;
    public NoteRepository(Context context) {
        noteDatabase = NoteDatabase.getInstance(context);
        goalDao = noteDatabase.goalDao();
        allGoals = goalDao.getAllGoals();
        this.context = context;
    }

    public void insertGoal(Goal goal){
        new InsertGoalAsyncTask(goalDao).execute(goal);
    }

    public void deleteGoal(Goal goal){
        new DeleteGoalAsyncTask(goalDao).execute(goal);
    }

    public void updateGoal(Goal goal){
        new UpdateGoalAsyncTask(goalDao).execute(goal);
    }

    public void deleteAllGoals(){
        new DeleteAllGoalAsyncTask(goalDao).execute();
    }

    public LiveData<List<Goal>> getAllGoals(){
        return allGoals;

    }

    private static class InsertGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
        private GoalDao goalDao;

        private InsertGoalAsyncTask(GoalDao goalDao){
            this.goalDao = goalDao;
        }

        @Override
        protected Void doInBackground(Goal... goals) {
            goalDao.insert(goals[0]);
            return null;
        }
    }

    private static class DeleteGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
        private GoalDao goalDao;

        private DeleteGoalAsyncTask(GoalDao goalDao){
            this.goalDao = goalDao;
        }

        @Override
        protected Void doInBackground(Goal... goals) {
            goalDao.delete(goals[0]);
            return null;
        }
    }

    private static class UpdateGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
        private GoalDao goalDao;

        private UpdateGoalAsyncTask(GoalDao goalDao){
            this.goalDao = goalDao;
        }

        @Override
        protected Void doInBackground(Goal... goals) {
            goalDao.update(goals[0]);
            return null;
        }
    }

    private static class DeleteAllGoalAsyncTask extends AsyncTask<Void,Void,Void>{
        private GoalDao goalDao;

        private DeleteAllGoalAsyncTask(GoalDao goalDao){
            this.goalDao = goalDao;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            goalDao.deleteAllGoals();
            return null;
        }
    }
}

The DAO class: DAO class:

@Dao
public interface GoalDao {

    @Insert
    void insert(Goal goal);

    @Update
    void update(Goal goal);

    @Delete
    void delete(Goal goal);

    @Query("DELETE from goal_table")
    void deleteAllGoals();

    @Query("Select * from goal_table order by end_date")
    LiveData<List<Goal>> getAllGoals();

}

I have this issue in 2 fragments and there are 2 other fragments that do not have this issue with the exact same implementation.我在 2 个片段中有这个问题,还有 2 个其他片段在完全相同的实现中没有这个问题。 Why is the observer not being called as soon as I update a record in MyGoals fragment?为什么在我更新 MyGoals 片段中的记录后没有立即调用观察者?

I found the solution, the problem was not in the LiveData code, but in the Recyclerview ListAdapter & DiffUtil Implementation which stopped from triggering LiveData change.我找到了解决方案,问题不在 LiveData 代码中,而是在停止触发 LiveData 更改的 Recyclerview ListAdapter & DiffUtil 实现中。

In MyGoalsAdapter I have used DiffUtil & ListAdapter to have smooth animations and increase performance.在 MyGoalsAdapter 中,我使用了 DiffUtil 和 ListAdapter 来获得流畅的动画并提高性能。 For it to work properly we need to compare the new list with the old list.为了让它正常工作,我们需要将新列表与旧列表进行比较。 The Problem is where the contents of an object were being marked as equal when they were actually different.问题是 object 的内容实际上不同时被标记为相等。 I solved this by adding a date field in my Model class modifiedAt and updated the field before that Object was updated.我通过在我的 Model class modifiedAt中添加一个日期字段来解决这个问题,并在更新 Object 之前更新了该字段。 Here is the snippet of code to explain it better.这是代码片段,可以更好地解释它。

MyGoalsAdapter:我的目标适配器:

public class MyGoalsAdapter extends ListAdapter<Goal, MyGoalsAdapter.MyGoalsViewHolder> {
    private Context context;

    public MyGoalsAdapter() {
        super(DIFF_CALLBACK);
    }

    private static final DiffUtil.ItemCallback<Goal> DIFF_CALLBACK = new DiffUtil.ItemCallback<Goal>() {
        @Override
        public boolean areItemsTheSame(@NonNull Goal oldItem, @NonNull Goal newItem) {
            return oldItem.getId() == newItem.getId();
        }

        @Override
        public boolean areContentsTheSame(@NonNull Goal oldItem, @NonNull Goal newItem) { //Here we check if the objects in the list have changed fields.
            boolean id,desc,iscomp,edate,etime,sdate,stime,title, naya, purana, createdAt, modifiedAt;

            id = oldItem.getId() == newItem.getId();
            desc = oldItem.getDescription().equals(newItem.getDescription());
            purana = oldItem.isCompleted();
            naya = newItem.isCompleted();
            iscomp = purana && naya;
            edate =  oldItem.getEnd_date().equals(newItem.getEnd_date());
            etime = oldItem.getEnd_time().equals(newItem.getEnd_time());
            sdate = oldItem.getStart_date().equals(newItem.getStart_date());
            stime = oldItem.getStart_time().equals(newItem.getStart_time());
            title = oldItem.getTitle().equals(newItem.getTitle());
            createdAt = oldItem.getCreatedAt().equals(newItem.getCreatedAt());
            modifiedAt = oldItem.getModifiedAt().equals(newItem.getModifiedAt()); //This will return false for the object that is changed 

            return id &&
                    desc &&
                    iscomp &&
                    edate &&
                    etime &&
                    sdate &&
                    stime &&
                    title &&
                    createdAt &&
                    modifiedAt
                    ;
        }
    };
}

When I am updating I set the Object modifiedAt field with the current Date and Time.当我更新时,我将 Object modifiedAt字段设置为当前日期和时间。

Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition()); //Get the object to make change to it

//make change to the object's field

tempGoal.setModifiedAt(Calendar.getInstance().getTime()); //set the modified date with Current date
myGoalsViewModel.updateGoal(tempGoal); //Update the object to the database


Changing the modifiedAt field will tell the Adapter when there is an object that is updated, triggering the animation and showing the updated object in the List, instantly.更改modifiedAt字段将告诉适配器何时有更新的 object,触发 animation 并立即在列表中显示更新的 object。

I hope this will help someone.我希望这会对某人有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM