Home > Software design >  OnBindViewHolder: why sometimes position don't start from zero?
OnBindViewHolder: why sometimes position don't start from zero?

Time:02-16

I'm using a RecyclerView and I noticed a strange behavior: I put a log every time onBindViewHolder() is called logging the actual position and sometimes the position don't start from zero, do you know why? For me this is a problem cause in position 0 I have a different logic.

That's the logger:

2022-02-15 16:19:51.833 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:19:51.834 D/UpdateFragment: >>>ViewModel.GetAllTags()
2022-02-15 16:19:51.864 D/RecyclerViewAdapterUpdate: >>>Position:0
2022-02-15 16:19:52.110 D/RecyclerViewAdapterUpdate: >>>Position:1
2022-02-15 16:19:52.266 D/RecyclerViewAdapterUpdate: >>>Position:2
2022-02-15 16:19:52.331 D/RecyclerViewAdapterUpdate: >>>Position:3
2022-02-15 16:20:03.696 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.722 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.729 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.729 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.730 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.737 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.743 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.750 D/UpdateFragment: >>>ViewModel.GetAllCards()
2022-02-15 16:20:03.761 D/RecyclerViewAdapterUpdate: >>>Position:2
2022-02-15 16:20:03.830 D/RecyclerViewAdapterUpdate: >>>Position:3
2022-02-15 16:20:03.891 D/RecyclerViewAdapterUpdate: >>>Position:1
2022-02-15 16:20:04.042 D/RecyclerViewAdapterUpdate: >>>Position:0

Ok, I understand the order isn't always the same, but in my app, when I update data I notice that there is a mysterious swap between 2 ViewHolder's layout, but in my code I never assign a layout to the ViewHolder. As you can see in the log the id of the layout associated with the position changes.

2022-02-15 19:03:10.878 D/RecyclerViewAdapterUpdate: >>>Position[0]android.widget.LinearLayout{83f4271 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:11.593 D/RecyclerViewAdapterUpdate: >>>Position[1]android.widget.LinearLayout{e951d5e G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:11.954 D/RecyclerViewAdapterUpdate: >>>Position[2]android.widget.LinearLayout{61b8127 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:18.326 D/RecyclerViewAdapterUpdate: >>>View android.widget.LinearLayout{61b8127 V.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}true
2022-02-15 19:03:18.953 D/RecyclerViewAdapterUpdate: >>>View androidx.constraintlayout.widget.ConstraintLayout{8510217 V.E...... ......I. 0,0-0,0 #7f08021e app:id/layout_update_card}true
2022-02-15 19:03:53.394 D/RecyclerViewAdapterUpdate: >>>Saved
2022-02-15 19:03:53.582 D/RecyclerViewAdapterUpdate: >>>Position[2]android.widget.LinearLayout{83f4271 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:53.729 D/RecyclerViewAdapterUpdate: >>>Position[1]android.widget.LinearLayout{e951d5e G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.005 D/RecyclerViewAdapterUpdate: >>>Position[0]android.widget.LinearLayout{61b8127 V.E...... .......D 0,98-720,761 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.437 D/RecyclerViewAdapterUpdate: >>>Position[0]android.widget.LinearLayout{61b8127 V.E...... .......D 0,98-720,378 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.695 D/RecyclerViewAdapterUpdate: >>>Position[1]android.widget.LinearLayout{e951d5e G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}
2022-02-15 19:03:54.879 D/RecyclerViewAdapterUpdate: >>>Position[2]android.widget.LinearLayout{83f4271 G.E...... ......I. 0,0-0,0 #7f0800f5 app:id/layout_cards}

public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        Log.d(TAG, ">>>Position[" position "]"   holder.linearLayout);
        if (position == 0) {

            // All the cards
            holder.numberOfItems.setText(String.valueOf(cardList.size()));
            holder.tagName.setText(R.string.all_cards_tag);
            holder.clearTag.setVisibility(View.GONE); // Can't delete all cards
            holder.checkBox.setChecked(allCardIsChecked);
            holder.checkBox.setOnClickListener(view -> {

                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putInt(Utilities.RECYCLER_CARD_POSITION, 0);
                editor.putBoolean(Utilities.SHOULD_SHUFFLE, true);

                if (holder.checkBox.isChecked()) {

                    MainActivity.recyclerTagList.clear();
                    editor.putStringSet(Utilities.SELECTED_TAGS, new HashSet<>());
                    Log.d(TAG, ">>Tags selected: "   MainActivity.recyclerTagList);

                    numberOfSelected = 1;
                    if (!allCardIsChecked) {
                        allCardIsChecked = true;
                        notifyDataSetChanged();
                    }

                } else {
                    // Can't deselect all cards, at least one group chosen
                    holder.checkBox.setChecked(true);
                    Toast.makeText(context, R.string.min_one_tag, Toast.LENGTH_SHORT).show();
                }

                editor.commit();

            });

            initializeLayoutCards(cardList,null,  holder, true);

        } else {

            // Single TAG after all cards
            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);
                }
            }

            initializeLayoutCards(listOfSingleTag, tag, holder, false);

            holder.numberOfItems.setText(String.valueOf(listOfSingleTag.size()));
            holder.tagName.setText(tag.getTag());
            holder.clearTag.setVisibility(View.VISIBLE);
            if (allCardIsChecked)
                holder.checkBox.setChecked(false);
            else {
                holder.checkBox.setChecked(false);
                for (String s: selectedTags) {
                    if (s.equalsIgnoreCase(tag.getTag()))
                        holder.checkBox.setChecked(true);
                }
            }
            holder.checkBox.setOnClickListener(view -> {

                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putInt(Utilities.RECYCLER_CARD_POSITION, 0);
                editor.putBoolean(Utilities.SHOULD_SHUFFLE, true);

                if (holder.checkBox.isChecked()) {
                    MainActivity.recyclerTagList.add(tag);
                    selectedTags = sharedPreferences.getStringSet(Utilities.SELECTED_TAGS, new HashSet<>());
                    selectedTags.add(tag.getTag());
                    editor.putStringSet(Utilities.SELECTED_TAGS, selectedTags);
                    Log.d(TAG, ">>Tags selected: "   MainActivity.recyclerTagList);

                    if (allCardIsChecked) {
                        allCardIsChecked = false;
                        notifyDataSetChanged();
                    } else {
                          numberOfSelected;
                    }
                } else {
                    if (numberOfSelected == 1) {
                        Toast.makeText(context, R.string.min_one_tag, Toast.LENGTH_SHORT).show();
                        holder.checkBox.setChecked(true);
                    }
                    else {
                        --numberOfSelected;
                        MainActivity.recyclerTagList.removeIf(t -> t.getTag().equalsIgnoreCase(tag.getTag()));
                        selectedTags = sharedPreferences.getStringSet(Utilities.SELECTED_TAGS, new HashSet<>());
                        selectedTags.remove(tag.getTag());
                        editor.putStringSet(Utilities.SELECTED_TAGS, selectedTags);
                        Log.d(TAG, ">>Tags selected: "   MainActivity.recyclerTagList);
                    }
                }

                editor.commit();

            });

            View viewDialogTag = LayoutInflater.from(context).inflate(R.layout.dialog_modify_tag, (ViewGroup) 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 dialogTag = getDialogUpdateTag(tag, viewDialogTag, tagNameEditText);
            AlertDialog dialogDeleteTag = getDialogDeleteTag(tag);

            holder.tagName.setOnLongClickListener( view -> {
                // Change tag name only if closed
                if (holder.linearLayout.getVisibility() == View.GONE)
                    dialogTag.show();
                return true;
            });

            holder.clearTag.setOnClickListener( view -> dialogDeleteTag.show());

        }

    }

private void initializeLayoutCards(List<CardWithTags> cardList, Tag tag, ViewHolder holder, boolean isAllCards) {

        // Or it will add already added cards
        holder.linearLayout.removeAllViews();

        for (CardWithTags cwt : cardList) {

            Card card = cwt.getCard();

            View cardView = LayoutInflater.from(context).inflate(R.layout.card_update, holder.linearLayout, false);
            TextView textViewCardName = cardView.findViewById(R.id.card_name);
            Button clearButton = cardView.findViewById(R.id.clear_card);
            ConstraintLayout layoutCardUpdate = cardView.findViewById(R.id.layout_update_card);
            EditText titleEditText = cardView.findViewById(R.id.update_title_edit_text);
            EditText taboo1EditText = cardView.findViewById(R.id.update_taboo_1_edit_text);
            EditText taboo2EditText = cardView.findViewById(R.id.update_taboo_2_edit_text);
            EditText taboo3EditText = cardView.findViewById(R.id.update_taboo_3_edit_text);
            EditText taboo4EditText = cardView.findViewById(R.id.update_taboo_4_edit_text);
            EditText taboo5EditText = cardView.findViewById(R.id.update_taboo_5_edit_text);
            Button saveButton = cardView.findViewById(R.id.save_button);
            Button tagButton = cardView.findViewById(R.id.tag_button);

            textViewCardName.setText(card.getTitle());
            titleEditText.setText(card.getTitle());
            taboo1EditText.setText(card.getTabooWord1());
            taboo2EditText.setText(card.getTabooWord2());
            taboo3EditText.setText(card.getTabooWord3());
            taboo4EditText.setText(card.getTabooWord4());
            taboo5EditText.setText(card.getTabooWord5());

            saveButton.setOnClickListener(view -> {

                Animations.doReduceIncreaseAnimation(view);

                String title = titleEditText.getText().toString();
                String taboo1 = taboo1EditText.getText().toString();
                String taboo2 = taboo2EditText.getText().toString();
                String taboo3 = taboo3EditText.getText().toString();
                String taboo4 = taboo4EditText.getText().toString();
                String taboo5 = taboo5EditText.getText().toString();

                if (title.equalsIgnoreCase(card.getTitle()) &&
                    taboo1.equalsIgnoreCase(card.getTabooWord1()) &&
                    taboo2.equalsIgnoreCase(card.getTabooWord2()) &&
                    taboo3.equalsIgnoreCase(card.getTabooWord3()) &&
                    taboo4.equalsIgnoreCase(card.getTabooWord4()) &&
                    taboo5.equalsIgnoreCase(card.getTabooWord5())) {
                    Log.d(TAG, ">>Change something before saving card");
                    return;
                }

                Card newCard = new Card(title, taboo1, taboo2, taboo3, taboo4, taboo5);
                newCard.setIdCard(card.getIdCard());

                // Check if new title already exists
                for (CardWithTags c: this.cardList) {
                    if (!c.getCard().getTitle().equalsIgnoreCase(card.getTitle()) && c.getCard().getTitle().equalsIgnoreCase(title)) {
                        Toast.makeText(context, R.string.title_already_exists, Toast.LENGTH_SHORT).show();
                        return;
                    }
                }

                cwt.setCard(newCard);
                Log.d(TAG, ">>New cwt: "   cwt);
                Log.d(TAG, ">>>Saved");
                viewModelFragment.updateCWT(cwt);
                Toast.makeText(context, R.string.card_updated, Toast.LENGTH_SHORT).show();
            });

            tagButton.setOnClickListener(view -> {

                Animations.doReduceIncreaseAnimation(view);
            });

            textViewCardName.setOnClickListener(view -> {
                openCloseView(layoutCardUpdate);
            });

            AlertDialog dialogDeleteCard;
            if (!isAllCards)
                dialogDeleteCard = getDialogDeleteCardOrTag(card, tag);
            else
                dialogDeleteCard = getDialogDeleteCard(card);

            clearButton.setOnClickListener(view -> dialogDeleteCard.show());

            // Set the click on both number of items and tag name
            holder.tagName.setOnClickListener(view -> {
                openCloseView(holder.linearLayout);
            });
            holder.numberOfItems.setOnClickListener(view -> {
                openCloseView(holder.linearLayout);
            });

            holder.linearLayout.addView(cardView);

        }

    }

    private void openCloseView(View view) {

        if (view.getVisibility() == View.GONE) {

            view.setVisibility(View.VISIBLE);
            boolean isVisible = view.getVisibility() == View.VISIBLE? true : false;
            Log.d(TAG, ">>>View "   view  isVisible);

            AnimationSet animation = new AnimationSet(true);
            Animation animationAlpha = new AlphaAnimation(0, 1);
            animation.addAnimation(animationAlpha);
            Animation animationTranslate = new TranslateAnimation(0, 0, -view.getHeight(), 0);
            animation.addAnimation(animationTranslate);
            Animation animationY = new ScaleAnimation(1, 1, 0, 1);
            animationY.setInterpolator(new LinearInterpolator());
            animation.addAnimation(animationY);
            animation.setDuration(200);

            view.startAnimation(animation);

        } else {

            AnimationSet animation = new AnimationSet(true);
            Animation animationAlpha = new AlphaAnimation(1, 0);
            animation.addAnimation(animationAlpha);
            Animation animationTranslate = new TranslateAnimation(0, 0, 0, -view.getHeight());
            animation.addAnimation(animationTranslate);
            Animation animationY = new ScaleAnimation(1, 1, 1, 0);
            animationY.setInterpolator(new LinearInterpolator());
            animation.addAnimation(animationY);
            animation.setDuration(200);

            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    view.setVisibility(View.GONE);
                    boolean isVisible = view.getVisibility() == View.VISIBLE;
                    Log.d(TAG, ">>>View "   view  isVisible);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });

            view.startAnimation(animation);
        }
    }

CodePudding user response:

Only the items that are on screen and need to be updated are bound. In fact the component tries to minimize the number of onbind calls to prevent extra work. There is no promised order to the calls. So about the only time 0 is assured to be called is on first load- and even then there's situations where it won't (if you're doing a reverse fill and start from the bottom for example). If you've written you code assuming it will always try to bind 0, you've misunderstood how RecyclerView works and will need to reassess your design.

  • Related