Search code examples
javaandroidviewmodel

ViewModel datas don't update


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.


Solution

  • 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();
    
    }