Need an example about RecyclerView.Adapter.notifyItemChanged(int position, Object payload)

53,989

Solution 1

Check out this sample code that demonstrates the feature. It's a RecyclerView that calls notifyItemChanged(position, payload) when the item at position position is clicked. You can verify that onBindViewHolder(holder, position, payload) was called by looking for the logcat statement.

Make sure you are using at least version 23.1.1 of the support libraries, like so:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
    compile 'com.android.support:cardview-v7:23.1.1'
}

HelloActivity.java

package com.formagrid.hellotest;

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class HelloActivity extends Activity {

    private RecyclerView mRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mRecyclerView.setAdapter(new HelloAdapter());
        DefaultItemAnimator animator = new DefaultItemAnimator() {
            @Override
            public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
                return true;
            }
        };
        mRecyclerView.setItemAnimator(animator);
    }

    private static class HelloAdapter extends RecyclerView.Adapter<HelloAdapter.HelloViewHolder> {

        public class HelloViewHolder extends RecyclerView.ViewHolder {

            public TextView textView;

            public HelloViewHolder(CardView cardView) {
                super(cardView);
                textView = (TextView) cardView.findViewById(R.id.text_view);
            }

        }

        @Override
        public HelloViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            CardView cardView = (CardView) LayoutInflater.from(parent.getContext()).inflate(
                    R.layout.card_item, parent, false);
            return new HelloViewHolder(cardView);
        }

        @Override
        public void onBindViewHolder(HelloViewHolder holder, int position) {
            bind(holder);
        }

        @Override
        public void onBindViewHolder(HelloViewHolder holder, int position, List<Object> payload) {
            Log.d("butt", "payload " + payload.toString());
            bind(holder);
        }

        @Override
        public int getItemCount() {
            return 20;
        }

        private void bind(final HelloViewHolder holder) {
            holder.textView.setText("item " + holder.getAdapterPosition());
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    final int position = holder.getAdapterPosition();
                    Log.d("butt", "click " + position);
                    HelloAdapter.this.notifyItemChanged(position, "payload " + position);
                }
            });
        }

    }

}

activity_main.xml

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".HelloActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

card_item.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="100dip"
    android:layout_margin="5dip"
    card_view:cardElevation="5dip">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.v7.widget.CardView>

Solution 2

If you want to update not all holder View but just part of it, this method is what you need.

Image that you have following ViewHolder

public class ViewHolder extends RecyclerView.ViewHolder {
        public final TextView tvPlayer;
        public final TextView tvScore;

        public ViewHolder(View view) {
            super(view);
            tvPlayer = (TextView) view.findViewById(R.id.tv_player);
            tvScore = (TextView) view.findViewById(R.id.tv_score);
        }

    }

And somewhere in your code you call adapter to update single TextView - tvScore

mRecyclerViewAdapter.notifyItemChanged(position, new Integer(4533));

[...]

onBindViewHolder(ViewHolder holder, int position, List payloads) catches callback at first. If payloads doesn't match your requirements you have to obligatory call super class super.onBindViewHolder(holder,position, payloads); which trigger onBindViewHolder(ViewHolder holder, int position) for other cases.

     // Update only part of ViewHolder that you are interested in
     // Invoked before onBindViewHolder(ViewHolder holder, int position)
        @Override
        public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
            if(!payloads.isEmpty()) {
                if (payloads.get(0) instanceof Integer) {
                    holder.tvScore.setText(String.valueOf((Integer)payloads.get(0)))
                }
            }else {
                super.onBindViewHolder(holder,position, payloads);
            }
        }

    // Update ALL VIEW holder
    @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            MItem item = mList.get(position)
            // some update
        }

Solution 3

Basically, it's much more convenient to treat payloads as constants.
You don't have to pass any data there if it's already changed in the dataset.
So, create a couple of constants:

public static final String PAYLOAD_NAME = "PAYLOAD_NAME";
public static final String PAYLOAD_AGE = "PAYLOAD_AGE";

Then in the adapter, besides a regular onBindViewHolder(YourViweHolder holder, int position) where you update the whole item, for instance:

@Override
public void onBindViewHolder(final YourViewHolder holder, final int position) {
    final YourItem item = getItem(position);
    holder.tvName.setText(item.getName());
    holder.tvAge.setText(String.valueOf(item.getAge()));
}

you also implement:

@Override
public void onBindViewHolder(final YourViewHolder holder, final int position, final List<Object> payloads) {
    if (!payloads.isEmpty()) {
        final YourItem item = getItem(position);
        for (final Object payload : payloads) {
            if (payload.equals(PAYLOAD_NAME)) {
               // in this case only name will be updated
               holder.tvName.setText(item.getName());
            } else if (payload.equals(PAYLOAD_AGE)) {
               // only age will be updated
               holder.tvAge.setText(String.valueOf(item.getAge()));
            }
        }
    } else {
        // in this case regular onBindViewHolder will be called
        super.onBindViewHolder(holder, position, payloads);
    }
}

You can handle as many cases as you need and update only views that actually changed.
The last step - you just do:

adapter.notifyItemChanged(somePosition, YourAdapter.PAYLOAD_NAME);

Solution 4

You can pass in an "optional payload object" when you just want to perform a partial update to the data in the ViewHolder when the onBindViewHolder method is called. However, it is not sure that the payload will always be passed, for example if the view is not attached so you should perform some more checks.

Anyway, if you pass null it will be performed a full update to the item and you won't have to worry about it.

Share:
53,989
Admin
Author by

Admin

Updated on July 05, 2022

Comments

  • Admin
    Admin over 1 year

    According to RecyclerView documentation about medthod notifyItemChanged(int position, Object payload)

    Notify any registered observers that the item at position has changed with an optional payload object.

    I don't understand how to use second paramenter payload in this method. I have searched many document about "payload" but everything was ambiguous.

    So, If you know about this method, please show me a clear example about it. Thank you very much.

  • Cocorico
    Cocorico about 7 years
    I just tested your code. There is something useless in my test : the basic onBind (without payload) is never call. Do you now why ? I just call notifyItemRangeChanged(index, mArrayList.size()); or notifyItemInserted(i);
  • Tyler
    Tyler over 6 years
    love the immature log tag +1
  • android developer
    android developer almost 6 years
    Why is it a list of payloads, though? In which case do you have more than one object in the list? In case you call notifyItemChanged multiple times on the same position ?
  • murt
    murt almost 6 years
    If you are using Kotlin, there is hint that payload is type of Any and in Java is Object, so you may pass there anything you want. Later on you will cast your objects inside onBindViewHolder(ViewHolder holder, int position, List<Object> payloads). See more at: developer.android.com/reference/android/support/v7/widget/…, java.lang.Object)
  • Leonid Ustenko
    Leonid Ustenko over 5 years
    Yes, in a sense it's a Tag
  • mike-gallego
    mike-gallego about 5 years
    After hours of struggling to find a solution, this one worked. Thanks much :)
  • jeevashankar
    jeevashankar almost 2 years
    Yes it's working as expected with payloads. Thanks Leonid Ustenko
  • Farid
    Farid over 1 year
    @androiddeveloper You probably have already figured it out but for the future readers: Yes, if you notify the same position with multiple payloads they will be delivered as a list of those objects to onBindViewHolder()