Android 对记录数据进行更新时,不会触发 Fragment LiveData 观察器
Android Fragment LiveData observer is not triggered when update is done on a record data
我想弄清楚为什么 getAllGoals()
的 LiveData 观察器在我更新记录时没有在片段中立即触发。但是,只有在使用底部选项卡导航切换到另一个片段然后返回到原始片段后,才会调用观察者。
相关片段:
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 ViewModel:
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 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();
}
我在 2 个片段中遇到了这个问题,还有 2 个其他片段在完全相同的实现中没有这个问题。为什么在我更新 MyGoals 片段中的记录后没有立即调用观察者?
我找到了解决方案,问题不在 LiveData 代码中,而是在停止触发 LiveData 更改的 Recyclerview ListAdapter 和 DiffUtil 实现中。
在 MyGoalsAdapter 中,我使用了 DiffUtil 和 ListAdapter 来获得流畅的动画并提高性能。为了使其正常工作,我们需要将新列表与旧列表进行比较。问题是对象的内容实际上不同时却被标记为相等。我通过在我的模型 class modifiedAt
中添加一个日期字段来解决这个问题
并在更新该对象之前更新该字段。这是代码片段,可以更好地解释它。
我的目标适配器:
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
;
}
};
}
更新时,我将对象 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
字段将在有对象更新时通知适配器,立即触发动画并在列表中显示更新的对象。
我希望这会对某人有所帮助。
我想弄清楚为什么 getAllGoals()
的 LiveData 观察器在我更新记录时没有在片段中立即触发。但是,只有在使用底部选项卡导航切换到另一个片段然后返回到原始片段后,才会调用观察者。
相关片段: 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 ViewModel:
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 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();
}
我在 2 个片段中遇到了这个问题,还有 2 个其他片段在完全相同的实现中没有这个问题。为什么在我更新 MyGoals 片段中的记录后没有立即调用观察者?
我找到了解决方案,问题不在 LiveData 代码中,而是在停止触发 LiveData 更改的 Recyclerview ListAdapter 和 DiffUtil 实现中。
在 MyGoalsAdapter 中,我使用了 DiffUtil 和 ListAdapter 来获得流畅的动画并提高性能。为了使其正常工作,我们需要将新列表与旧列表进行比较。问题是对象的内容实际上不同时却被标记为相等。我通过在我的模型 class modifiedAt
中添加一个日期字段来解决这个问题
并在更新该对象之前更新该字段。这是代码片段,可以更好地解释它。
我的目标适配器:
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
;
}
};
}
更新时,我将对象 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
字段将在有对象更新时通知适配器,立即触发动画并在列表中显示更新的对象。
我希望这会对某人有所帮助。