ViewModel 数据不更新
ViewModel datas don't update
我有一个 UpdateFragment,它使用 RecyclerView 和 ViewModel 按标签分组显示卡片。在 UpdateFragment 中,我还可以修改标签的名称,但我不知道为什么在我重新创建片段之前 UpdateFragment 未检测到更改,因此 UI 不更新。奇怪的是,MainActivity 反而检测到列表的更新和代码相等。
UpdateFragment.class
public class UpdateFragment extends BaseCardFragment {
// TODO show total cards number
private static final String TAG = "UpdateFragment";
private RecyclerView recyclerView;
public UpdateFragment(List<CardWithTags> cardList, List<Tag> tagList) {
super(cardList, tagList);
}
@Override
public void updateUI(List<CardWithTags> cardListWithTags, List<Tag> tagList) {
RecyclerViewAdapterUpdate adapter = (RecyclerViewAdapterUpdate) recyclerView.getAdapter();
Log.d(TAG, "Adapter:" + adapter);
Log.d(TAG, "Cards:" + cardListWithTags);
Log.d(TAG, "Tags:" + tagList);
if (adapter != null && cardListWithTags != null && tagList != null) {
adapter.setCardList(cardListWithTags);
adapter.setTagList(tagList);
adapter.notifyDataSetChanged();
Log.d(TAG, ">>Update, Total cards: " + adapter.getItemCount());
Log.d(TAG, ">>Update, Total tags: " + tagList.size());
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_update, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setViewModel();
setRecyclerView();
}
private void setViewModel() {
Log.d(TAG, ">>SetViewModel()");
viewModel = new ViewModelProvider(this).get(ViewModelMainActivity.class);
viewModel.getAllCards().observe(getActivity(), cardListLoaded -> {
cardList = cardListLoaded;
Log.d(TAG, ">>CardList updated:" + cardList);
updateUI(cardList, tagList);
});
viewModel.getAllTags().observe(getActivity(), tagListLoaded -> {
tagList = tagListLoaded;
Log.d(TAG, ">>TagList updated:" + tagList);
updateUI(cardList, tagList);
});
}
private void setRecyclerView() {
Log.d(TAG, ">>SetRecyclerView()");
recyclerView = getView().findViewById(R.id.recycler_view_list);
RecyclerViewAdapterUpdate recyclerViewAdapter = new RecyclerViewAdapterUpdate(cardList, tagList,
getContext(), getLayoutInflater(),
viewModel);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(),
RecyclerView.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(recyclerViewAdapter);
}
}
ViewModelMainActivity.class
public class ViewModelMainActivity extends AndroidViewModel {
private static final String TAG = "ViewModelMainActivity";
private static boolean cardListIsUpdatedWithDb = false;
private static boolean tagListIsUpdatedWithDb = false;
private LiveData<List<CardWithTags>> cardList = new MutableLiveData<>();
private LiveData<List<Tag>> tagList = new MutableLiveData<>();
private final CardDAO cardDAO;
private final ExecutorService executor;
public ViewModelMainActivity(@NonNull Application application) {
super(application);
this.cardDAO = DatabaseTaboom.getDatabase(application).cardDao();
this.executor = Executors.newSingleThreadExecutor();
}
public LiveData<List<CardWithTags>> getAllCards() {
Log.d(TAG, ">>GetAllCards()");
if (!cardListIsUpdatedWithDb) {
cardList = cardDAO.getAllCards();
cardListIsUpdatedWithDb = true;
}
return cardList;
}
public LiveData<List<Tag>> getAllTags() {
Log.d(TAG, ">>GetAllTags()");
if (!tagListIsUpdatedWithDb) {
tagList = cardDAO.getAllTags();
tagListIsUpdatedWithDb = true;
}
return tagList;
}
public void insertCard(CardWithTags card) {
Log.d(TAG, ">>InsertCard(): " + card);
executor.execute(() -> {
long idCard = cardDAO.insertCard(card.getCard());
if (idCard >= 1) {
cardListIsUpdatedWithDb = false;
Log.d(TAG, ">>Inserted Card: " + card.getCard().getTitle());
}
for (Tag t: card.getTagList()) {
long idTag = cardDAO.insertTag(t);
CardTagCrossRef cardTagCrossRef = new CardTagCrossRef();
cardTagCrossRef.idCard = idCard;
if (idTag < 1) {
// If Tag already exist, retrieve that to get id to try insert cwt
idTag = cardDAO.getTag(t.getTag()).getIdTag();
} else {
// New Tag
tagListIsUpdatedWithDb = false;
Log.d(TAG, ">>Inserted Tag: " + t.getTag());
}
cardTagCrossRef.idTag = idTag;
long idCWT = cardDAO.insertCardWithTags(cardTagCrossRef);
if (idCWT >= 1) {
Log.d(TAG, ">>Inserted CWT: [" + idCard + "," + idTag + "]");
// Tag linked to a card
cardListIsUpdatedWithDb = false;
}
}
});
}
public void shuffle(List<CardWithTags> list) {
if (list != null) {
Collections.shuffle(list);
cardList = new MutableLiveData<>(list);
Log.d(TAG, ">>List shuffled: " + list);
} else {
Log.d(TAG, ">>List is null");
}
}
public void updateTag(Tag tag) {
executor.execute(() -> {
cardDAO.updateTag(tag);
tagListIsUpdatedWithDb = false;
cardListIsUpdatedWithDb = false;
});
}
}
RecyclerViewAdapterUpdate.class
public class RecyclerViewAdapterUpdate extends RecyclerView.Adapter<RecyclerViewAdapterUpdate.ViewHolder> {
private static final String TAG = "RecyclerViewAdapterUpdate";
private List<CardWithTags> cardList;
private List<Tag> tagList;
private Context context;
private LayoutInflater layoutInflater;
private ViewModelMainActivity viewModelFragment;
public RecyclerViewAdapterUpdate(List<CardWithTags> cardList, List<Tag> tagList, Context context,
LayoutInflater layoutInflater, ViewModelMainActivity viewModelFragment) {
this.cardList = cardList;
this.tagList = tagList;
this.context = context;
this.layoutInflater = layoutInflater;
this.viewModelFragment = viewModelFragment;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_update, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (position == 0) {
holder.numberOfItems.setText(String.valueOf(cardList.size()));
holder.tagName.setText(R.string.all_cards_tag);
holder.clearTag.setVisibility(View.GONE);
} else {
Tag tag = tagList.get(position - 1);
List<CardWithTags> listOfSingleTag = new ArrayList<>();
for (CardWithTags cwt: cardList) {
for (Tag t: cwt.getTagList()) {
if (t.getTag().equals(tag.getTag()))
listOfSingleTag.add(cwt);
}
}
holder.numberOfItems.setText(String.valueOf(listOfSingleTag.size()));
holder.tagName.setText(tag.getTag());
View viewDialogTag = layoutInflater.inflate(R.layout.dialog_modify_tag, null);
EditText tagNameEditText = viewDialogTag.findViewById(R.id.tag_name_dialog);
tagNameEditText.setText(tag.getTag());
// In this way the dialog is created only one time
AlertDialog dialog = new AlertDialog.Builder(context)
.setView(viewDialogTag)
.setTitle(R.string.title_modify_tag)
.setPositiveButton(R.string.modify, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String newTag = tagNameEditText.getText().toString();
if (!newTag.isEmpty()&& !newTag.equalsIgnoreCase(tag.getTag())) {
tag.setTag(newTag);
viewModelFragment.updateTag(tag);
Log.d(TAG, ">>Update TAG: " + tag.getTag() + "->" + newTag);
}
}
})
.create();
holder.tagName.setOnLongClickListener( view -> {
dialog.show();
return true;
});
holder.clearTag.setOnClickListener( v -> Log.d(TAG, ">>CLICKED"));
}
}
@Override
public int getItemCount() {
if (tagList == null)
return 1;
return tagList.size() + 1;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "ViewHolder";
private final TextView numberOfItems;
private final TextView tagName;
private final Button clearTag;
private final CheckBox checkBox;
public ViewHolder(@NonNull View itemView) {
super(itemView);
numberOfItems = itemView.findViewById(R.id.number_of_items);
tagName = itemView.findViewById(R.id.tag_name);
clearTag = itemView.findViewById(R.id.clear_tag);
checkBox = itemView.findViewById(R.id.check_box);
}
}
public void setCardList(List<CardWithTags> cardList) {
this.cardList = cardList;
}
public void setTagList(List<Tag> tagList) {
this.tagList = tagList;
}
}
我不明白为什么不调用这段代码:
viewModel.getAllCards().observe(getActivity(), cardListLoaded -> {
cardList = cardListLoaded;
Log.d(TAG, ">>CardList updated:" + cardList);
updateUI(cardList, tagList);
});
viewModel.getAllTags().observe(getActivity(), tagListLoaded -> {
tagList = tagListLoaded;
Log.d(TAG, ">>TagList updated:" + tagList);
updateUI(cardList, tagList);
});
MainActivity.class 方法被调用,即使它等于 UpdateFragment 中的那个方法(activity 是相同的):
private void firstLoadCardList() {
ConstraintLayout fragmentContainer = findViewById(R.id.fragment_container);
ConstraintLayout progressBarContainer = findViewById(R.id.loading_container);
fragmentContainer.setVisibility(View.GONE);
progressBarContainer.setVisibility(View.VISIBLE);
Log.d(TAG, ">>FirstLoadCardList()");
viewModel = new ViewModelProvider(this).get(ViewModelMainActivity.class);
checkIfDatabaseAlreadyExist();
viewModel.getAllCards().observe(this, cardListLoaded -> {
cardList = cardListLoaded;
Log.d(TAG, ">>Total cards found: " + cardList.size());
if (tagList != null /*&& appJustOpened*/) {
appJustOpened = false;
progressBarContainer.setVisibility(View.GONE);
fragmentContainer.setVisibility(View.VISIBLE);
bottomNav.setSelectedItemId(R.id.play_nav);
}
});
viewModel.getAllTags().observe(this, tagListLoaded -> {
tagList = tagListLoaded;
Log.d(TAG, ">>Total tags found: " + tagList.size());
// If tagList not loaded before creation of BottomNav update Fragment list
// and cardList already loaded
if (cardList!= null /*&& appJustOpened*/) {
appJustOpened = false;
progressBarContainer.setVisibility(View.GONE);
fragmentContainer.setVisibility(View.VISIBLE);
bottomNav.setSelectedItemId(R.id.play_nav);
}
});
}
请停止在评论中谈论 notifyDataSetChanged(),我知道它是如何工作的,我的问题是 ViewModel/observer。关于观察者的部分不应该在每次 ViewModel 列表更改时调用吗?谢谢。
我认为您只需要在 recycler 适配器中添加以下功能即可。
public void setCardList(List<CardWithTags> cardList){
this.cardList=new ArrayList<CardWithTags>()
this.cardList.addAll(cardList)
notifyDataSetChanged()
}
public void setTagList(List<Tag> tagList){
this.tagList=new ArrayList<Tag>();
this.tagList.addAll(tagList);
notifyDataSetChanged();
}
终于找到了答案:两个观察者方法不相等。事实上,在 Activity 和片段中我都写道: viewModel.getAllCards().observe(this, cardListLoaded -> {...}
但是 this 如果从片段中的 Activity 或片段调用则不同我必须改用 requireActivity()
。
我有一个 UpdateFragment,它使用 RecyclerView 和 ViewModel 按标签分组显示卡片。在 UpdateFragment 中,我还可以修改标签的名称,但我不知道为什么在我重新创建片段之前 UpdateFragment 未检测到更改,因此 UI 不更新。奇怪的是,MainActivity 反而检测到列表的更新和代码相等。
UpdateFragment.class
public class UpdateFragment extends BaseCardFragment {
// TODO show total cards number
private static final String TAG = "UpdateFragment";
private RecyclerView recyclerView;
public UpdateFragment(List<CardWithTags> cardList, List<Tag> tagList) {
super(cardList, tagList);
}
@Override
public void updateUI(List<CardWithTags> cardListWithTags, List<Tag> tagList) {
RecyclerViewAdapterUpdate adapter = (RecyclerViewAdapterUpdate) recyclerView.getAdapter();
Log.d(TAG, "Adapter:" + adapter);
Log.d(TAG, "Cards:" + cardListWithTags);
Log.d(TAG, "Tags:" + tagList);
if (adapter != null && cardListWithTags != null && tagList != null) {
adapter.setCardList(cardListWithTags);
adapter.setTagList(tagList);
adapter.notifyDataSetChanged();
Log.d(TAG, ">>Update, Total cards: " + adapter.getItemCount());
Log.d(TAG, ">>Update, Total tags: " + tagList.size());
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_update, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setViewModel();
setRecyclerView();
}
private void setViewModel() {
Log.d(TAG, ">>SetViewModel()");
viewModel = new ViewModelProvider(this).get(ViewModelMainActivity.class);
viewModel.getAllCards().observe(getActivity(), cardListLoaded -> {
cardList = cardListLoaded;
Log.d(TAG, ">>CardList updated:" + cardList);
updateUI(cardList, tagList);
});
viewModel.getAllTags().observe(getActivity(), tagListLoaded -> {
tagList = tagListLoaded;
Log.d(TAG, ">>TagList updated:" + tagList);
updateUI(cardList, tagList);
});
}
private void setRecyclerView() {
Log.d(TAG, ">>SetRecyclerView()");
recyclerView = getView().findViewById(R.id.recycler_view_list);
RecyclerViewAdapterUpdate recyclerViewAdapter = new RecyclerViewAdapterUpdate(cardList, tagList,
getContext(), getLayoutInflater(),
viewModel);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(),
RecyclerView.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(recyclerViewAdapter);
}
}
ViewModelMainActivity.class
public class ViewModelMainActivity extends AndroidViewModel {
private static final String TAG = "ViewModelMainActivity";
private static boolean cardListIsUpdatedWithDb = false;
private static boolean tagListIsUpdatedWithDb = false;
private LiveData<List<CardWithTags>> cardList = new MutableLiveData<>();
private LiveData<List<Tag>> tagList = new MutableLiveData<>();
private final CardDAO cardDAO;
private final ExecutorService executor;
public ViewModelMainActivity(@NonNull Application application) {
super(application);
this.cardDAO = DatabaseTaboom.getDatabase(application).cardDao();
this.executor = Executors.newSingleThreadExecutor();
}
public LiveData<List<CardWithTags>> getAllCards() {
Log.d(TAG, ">>GetAllCards()");
if (!cardListIsUpdatedWithDb) {
cardList = cardDAO.getAllCards();
cardListIsUpdatedWithDb = true;
}
return cardList;
}
public LiveData<List<Tag>> getAllTags() {
Log.d(TAG, ">>GetAllTags()");
if (!tagListIsUpdatedWithDb) {
tagList = cardDAO.getAllTags();
tagListIsUpdatedWithDb = true;
}
return tagList;
}
public void insertCard(CardWithTags card) {
Log.d(TAG, ">>InsertCard(): " + card);
executor.execute(() -> {
long idCard = cardDAO.insertCard(card.getCard());
if (idCard >= 1) {
cardListIsUpdatedWithDb = false;
Log.d(TAG, ">>Inserted Card: " + card.getCard().getTitle());
}
for (Tag t: card.getTagList()) {
long idTag = cardDAO.insertTag(t);
CardTagCrossRef cardTagCrossRef = new CardTagCrossRef();
cardTagCrossRef.idCard = idCard;
if (idTag < 1) {
// If Tag already exist, retrieve that to get id to try insert cwt
idTag = cardDAO.getTag(t.getTag()).getIdTag();
} else {
// New Tag
tagListIsUpdatedWithDb = false;
Log.d(TAG, ">>Inserted Tag: " + t.getTag());
}
cardTagCrossRef.idTag = idTag;
long idCWT = cardDAO.insertCardWithTags(cardTagCrossRef);
if (idCWT >= 1) {
Log.d(TAG, ">>Inserted CWT: [" + idCard + "," + idTag + "]");
// Tag linked to a card
cardListIsUpdatedWithDb = false;
}
}
});
}
public void shuffle(List<CardWithTags> list) {
if (list != null) {
Collections.shuffle(list);
cardList = new MutableLiveData<>(list);
Log.d(TAG, ">>List shuffled: " + list);
} else {
Log.d(TAG, ">>List is null");
}
}
public void updateTag(Tag tag) {
executor.execute(() -> {
cardDAO.updateTag(tag);
tagListIsUpdatedWithDb = false;
cardListIsUpdatedWithDb = false;
});
}
}
RecyclerViewAdapterUpdate.class
public class RecyclerViewAdapterUpdate extends RecyclerView.Adapter<RecyclerViewAdapterUpdate.ViewHolder> {
private static final String TAG = "RecyclerViewAdapterUpdate";
private List<CardWithTags> cardList;
private List<Tag> tagList;
private Context context;
private LayoutInflater layoutInflater;
private ViewModelMainActivity viewModelFragment;
public RecyclerViewAdapterUpdate(List<CardWithTags> cardList, List<Tag> tagList, Context context,
LayoutInflater layoutInflater, ViewModelMainActivity viewModelFragment) {
this.cardList = cardList;
this.tagList = tagList;
this.context = context;
this.layoutInflater = layoutInflater;
this.viewModelFragment = viewModelFragment;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_update, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (position == 0) {
holder.numberOfItems.setText(String.valueOf(cardList.size()));
holder.tagName.setText(R.string.all_cards_tag);
holder.clearTag.setVisibility(View.GONE);
} else {
Tag tag = tagList.get(position - 1);
List<CardWithTags> listOfSingleTag = new ArrayList<>();
for (CardWithTags cwt: cardList) {
for (Tag t: cwt.getTagList()) {
if (t.getTag().equals(tag.getTag()))
listOfSingleTag.add(cwt);
}
}
holder.numberOfItems.setText(String.valueOf(listOfSingleTag.size()));
holder.tagName.setText(tag.getTag());
View viewDialogTag = layoutInflater.inflate(R.layout.dialog_modify_tag, null);
EditText tagNameEditText = viewDialogTag.findViewById(R.id.tag_name_dialog);
tagNameEditText.setText(tag.getTag());
// In this way the dialog is created only one time
AlertDialog dialog = new AlertDialog.Builder(context)
.setView(viewDialogTag)
.setTitle(R.string.title_modify_tag)
.setPositiveButton(R.string.modify, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String newTag = tagNameEditText.getText().toString();
if (!newTag.isEmpty()&& !newTag.equalsIgnoreCase(tag.getTag())) {
tag.setTag(newTag);
viewModelFragment.updateTag(tag);
Log.d(TAG, ">>Update TAG: " + tag.getTag() + "->" + newTag);
}
}
})
.create();
holder.tagName.setOnLongClickListener( view -> {
dialog.show();
return true;
});
holder.clearTag.setOnClickListener( v -> Log.d(TAG, ">>CLICKED"));
}
}
@Override
public int getItemCount() {
if (tagList == null)
return 1;
return tagList.size() + 1;
}
public class ViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "ViewHolder";
private final TextView numberOfItems;
private final TextView tagName;
private final Button clearTag;
private final CheckBox checkBox;
public ViewHolder(@NonNull View itemView) {
super(itemView);
numberOfItems = itemView.findViewById(R.id.number_of_items);
tagName = itemView.findViewById(R.id.tag_name);
clearTag = itemView.findViewById(R.id.clear_tag);
checkBox = itemView.findViewById(R.id.check_box);
}
}
public void setCardList(List<CardWithTags> cardList) {
this.cardList = cardList;
}
public void setTagList(List<Tag> tagList) {
this.tagList = tagList;
}
}
我不明白为什么不调用这段代码:
viewModel.getAllCards().observe(getActivity(), cardListLoaded -> {
cardList = cardListLoaded;
Log.d(TAG, ">>CardList updated:" + cardList);
updateUI(cardList, tagList);
});
viewModel.getAllTags().observe(getActivity(), tagListLoaded -> {
tagList = tagListLoaded;
Log.d(TAG, ">>TagList updated:" + tagList);
updateUI(cardList, tagList);
});
MainActivity.class 方法被调用,即使它等于 UpdateFragment 中的那个方法(activity 是相同的):
private void firstLoadCardList() {
ConstraintLayout fragmentContainer = findViewById(R.id.fragment_container);
ConstraintLayout progressBarContainer = findViewById(R.id.loading_container);
fragmentContainer.setVisibility(View.GONE);
progressBarContainer.setVisibility(View.VISIBLE);
Log.d(TAG, ">>FirstLoadCardList()");
viewModel = new ViewModelProvider(this).get(ViewModelMainActivity.class);
checkIfDatabaseAlreadyExist();
viewModel.getAllCards().observe(this, cardListLoaded -> {
cardList = cardListLoaded;
Log.d(TAG, ">>Total cards found: " + cardList.size());
if (tagList != null /*&& appJustOpened*/) {
appJustOpened = false;
progressBarContainer.setVisibility(View.GONE);
fragmentContainer.setVisibility(View.VISIBLE);
bottomNav.setSelectedItemId(R.id.play_nav);
}
});
viewModel.getAllTags().observe(this, tagListLoaded -> {
tagList = tagListLoaded;
Log.d(TAG, ">>Total tags found: " + tagList.size());
// If tagList not loaded before creation of BottomNav update Fragment list
// and cardList already loaded
if (cardList!= null /*&& appJustOpened*/) {
appJustOpened = false;
progressBarContainer.setVisibility(View.GONE);
fragmentContainer.setVisibility(View.VISIBLE);
bottomNav.setSelectedItemId(R.id.play_nav);
}
});
}
请停止在评论中谈论 notifyDataSetChanged(),我知道它是如何工作的,我的问题是 ViewModel/observer。关于观察者的部分不应该在每次 ViewModel 列表更改时调用吗?谢谢。
我认为您只需要在 recycler 适配器中添加以下功能即可。
public void setCardList(List<CardWithTags> cardList){
this.cardList=new ArrayList<CardWithTags>()
this.cardList.addAll(cardList)
notifyDataSetChanged()
}
public void setTagList(List<Tag> tagList){
this.tagList=new ArrayList<Tag>();
this.tagList.addAll(tagList);
notifyDataSetChanged();
}
终于找到了答案:两个观察者方法不相等。事实上,在 Activity 和片段中我都写道: viewModel.getAllCards().observe(this, cardListLoaded -> {...}
但是 this 如果从片段中的 Activity 或片段调用则不同我必须改用 requireActivity()
。