Android Обозреватель LiveData фрагментов не запускается при обновлении данных записи - PullRequest
0 голосов
/ 03 августа 2020

Я пытаюсь выяснить, почему наблюдатель LiveData для getAllGoals() не запускается сразу во фрагменте, когда я обновляю запись. Однако наблюдатель вызывается только после переключения на другой фрагмент с использованием навигации нижней вкладки и последующего возврата к исходному фрагменту.

Рассматриваемый фрагмент: 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);
    }

}


Модель представления 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);}

}

Репозиторий:

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

Класс DAO:

@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();

}

У меня эта проблема состоит из 2 фрагментов, и их 2 другие фрагменты, у которых нет этой проблемы, с той же самой реализацией. Почему наблюдатель не вызывается, как только я обновляю запись во фрагменте MyGoals?

1 Ответ

0 голосов
/ 07 августа 2020

Я нашел решение, проблема была не в коде LiveData, а в реализации Recyclerview ListAdapter и DiffUtil, которая перестала запускать изменение LiveData.

В MyGoalsAdapter я использовал DiffUtil и ListAdapter для плавной анимации и повысить производительность. Чтобы он работал правильно, нам нужно сравнить новый список со старым. Проблема заключается в том, что содержимое объекта помечается как равное, хотя на самом деле оно различно. Я решил это, добавив поле даты в свой класс модели modifiedAt и обновив поле до того, как этот объект был обновлен. Вот фрагмент кода, чтобы лучше это объяснить.

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

Когда я обновляю, я устанавливаю в поле 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


Изменение поля modifiedAt сообщит адаптеру, когда есть объект, который обновляется, мгновенно запустив анимацию и отобразив обновленный объект в списке.

Надеюсь, это поможет кто-то.

...