I have an UpdateFragment which display cards group by tags using a RecyclerView and ViewModel. In UpdateFragment I can also modify the name of the tags but I don't know why the change isn't detected by UpdateFragment until I recreate the fragment so UI don't update. The strange thing is that the MainActivity instead detect the update of the list and the code is equal.
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;
}
}
I don't understand why this piece of code isn't called:
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 method that instead is called even if is equal to that one in UpdateFragment (the activity is the same):
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);
}
});
}
Please stop talking about notifyDataSetChanged() in the comments, I know how it works, my problem is with ViewModel/observer. The part about observer shouldn't be called every time the lists of the ViewModel change? Thank you.
I think you just need to add the below function in your recycler adapter.
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();
}