Search code examples
androidandroid-recyclerviewadapter

Recycler View not saving state of Views


I am working on Recycler view in which I am using LinearLayout in adapter. When clicking on LinearLayout it is hiding View and similarly clicking again making it Visible. My problem is that Recycler view is not holding the state of Views, when I am scrolling it is again refreshing View again changing the state Views of initial time.

My RecyclerView code along with my Adapter is given below.

RecyclerView Code:

 LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
  Menu_Detail_Adapter  searchabledapter = new Menu_Detail_Adapter(getActivity());
   RecyclerView mRecyclerView =(RecyclerView)view.findViewById(R.id.recycler_view);
    mRecyclerView.setHasFixedSize(true);
    mRecyclerView.setItemViewCacheSize(2);
    mRecyclerView.setAdapter(searchabledapter);
    mRecyclerView.setLayoutManager(layoutManager);

My Adapter Code:

public class Menu_Detail_Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Filterable {


    public Menu_Detail_Adapter(Context context1) {
        arrayList = new ArrayList<>();
        arrlist = new ArrayList<>();
        context = context1;
    }




    @Override
    public RecyclerView.ViewHolder  onCreateViewHolder(ViewGroup parent, int viewType) {


            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.kasib_o_karahi_list_view, parent, false);
            return new MemberViewHolder(view, onItemClickListener);

    }



    String imageUrl = "";
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder  holder, int position) {

        final ParseObject parseObject = arrayList.get(position);

        if(holder instanceof  MemberViewHolder){

            if (arrayList.size() > 0) {
                try {

                    ((MemberViewHolder) holder).linear_layout__add.setVisibility(View.VISIBLE);
                    ((MemberViewHolder) holder).linear_layout_add_subtract_buttons.setVisibility(View.GONE);

                    // Here I am facing a problem

                    // On both LinearLayout click listeners when I am scrolling it 
is again changing my View States


                    ((MemberViewHolder) holder).rrl_add.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ((MemberViewHolder) holder).linear_layout__add.setVisibility(View.GONE);
                            ((MemberViewHolder) holder).linear_layout_add_subtract_buttons.setVisibility(View.VISIBLE);
                        }
                    });

                    ((MemberViewHolder) holder).rrl_minus.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ((MemberViewHolder) holder).linear_layout__add.setVisibility(View.VISIBLE);
                            ((MemberViewHolder) holder).linear_layout_add_subtract_buttons.setVisibility(View.GONE);
                        }
                    });

                    ((MemberViewHolder) holder).rrl_add_btn.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {

                        }
                    });



                } catch (Exception e) {
                    e.getMessage();
                }
            }
        }
    }

    @Override
    public int getItemCount() {
        return arrayList == null ? 0 : arrayList.size();
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
    }

    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
    }




    static class MemberViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        TextView txtTitle;
        TextView txtDescription;
        TextView txtCountry;
        OnItemClickListener onItemClickListener;
        ImageView imgDescription;

        LinearLayout linear_layout_add_subtract_buttons, linear_layout__add;
        RelativeLayout rrl_add, rrl_minus, rrl_add_btn;

        public MemberViewHolder(View itemView, OnItemClickListener onItemClickListener) {
            super(itemView);
            linear_layout_add_subtract_buttons = (LinearLayout) itemView.findViewById(R.id.add_subtract_buttons);
            linear_layout__add                 = (LinearLayout) itemView.findViewById(R.id.ll_add);
            rrl_add              = (RelativeLayout) itemView.findViewById(R.id.rrl_add);
            rrl_minus            = (RelativeLayout) itemView.findViewById(R.id.rrl_minus);
            rrl_add_btn          = (RelativeLayout) itemView.findViewById(R.id.rrl_add_btn);


            itemView.setOnClickListener(this);
            this.onItemClickListener = onItemClickListener;
        }

        @Override
        public void onClick(View v) {
            onItemClickListener.onItemClick(v, getAdapterPosition());
        }
    }

    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }


}

Solution

  • One problem is that your code is neither minimal nor complete and verifiable. So I had to make up an example which you will have to adopt to your own needs.

    The "single array of objects" I was talking about consists of objects of the class DataItem with one int and two booleans members.

    public class DataItem{
        int dataItem;
        boolean visible1;
        boolean visible2;
    
        public DataItem(int dataItem, boolean visible1, boolean visible2){
            this.dataItem = dataItem;
            this.visible1 = visible1;
            this.visible2 = visible2;
        }
    }
    

    onCreate() inside my activity looks like this:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);
    
        int dataItemsLength = 50;
        ArrayList<DataItem> dataItems = new ArrayList<>();
        for (int i = 0; i < dataItemsLength; i++) {
            dataItems.add(new DataItem(i, true, true));
        }
        RecyclerView rv = findViewById(R.id.rv);
        Menu_Detail_Adapter adapter = new Menu_Detail_Adapter(this, dataItems);
        rv.setLayoutManager(new LinearLayoutManager(this));
        rv.setAdapter(adapter);
    }
    

    While in your code it is unclear where your data comes from, here I chose the approach to "make" the data (you will probably want to "read" it) in the activity and pass it to the adapter. In principal, you could also do this inside the adapter.

    I used a very simple layout for the RecyclerView:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </RelativeLayout>
    

    And an only slightly more complex layout for the items:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
        <RelativeLayout
            android:id="@+id/rl1"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:background="@android:color/holo_orange_light">
    
           <TextView
                android:id="@+id/tv1"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </RelativeLayout>
    
        <RelativeLayout
            android:id="@+id/rl2"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:background="@android:color/holo_orange_dark">
    
            <TextView
                android:id="@+id/tv2"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </RelativeLayout>
    </LinearLayout>
    

    And, most importantly, the adapter:

    public class Menu_Detail_Adapter extends RecyclerView.Adapter<Menu_Detail_Adapter.ViewHolder> {
        private ArrayList<DataItem> dataItems;
        private final LayoutInflater inflater;
    
        Menu_Detail_Adapter(Context context, ArrayList<DataItem> dataItems) {
            this.dataItems = new ArrayList<>();
            this.dataItems.addAll(dataItems);
            this.inflater = LayoutInflater.from(context);
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolder(inflater.inflate(R.layout.list_item, parent, false));
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            holder.rl1.setVisibility(dataItems.get(position).visible1 ? View.VISIBLE : View.INVISIBLE);
            holder.tv1.setText(String.valueOf(dataItems.get(position).dataItem));
            holder.rl2.setVisibility(dataItems.get(position).visible2 ? View.VISIBLE : View.INVISIBLE);
        }
    
        @Override
        public int getItemCount() {
            return dataItems.size();
        }
    
        public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
            RelativeLayout rl1, rl2;
            TextView tv1;
    
            ViewHolder(View itemView) {
                super(itemView);
                rl1 = itemView.findViewById(R.id.rl1);
                rl2 = itemView.findViewById(R.id.rl2);
                rl1.setOnClickListener(this);
                rl2.setOnClickListener(this);
                tv1 = itemView.findViewById(R.id.tv1);
                ((TextView) itemView.findViewById(R.id.tv2)).setText("default");
            }
    
            @Override
            public void onClick(View view) {
                if (view.getId() == R.id.rl1) {
                    if (view.getVisibility() == View.VISIBLE) {
                        view.setVisibility(View.INVISIBLE);
                        dataItems.get(getAdapterPosition()).visible1 = false;
                    } else {
                        view.setVisibility(View.VISIBLE);
                        dataItems.get(getAdapterPosition()).visible1 = true;
                    }
                } else if (view.getId() == R.id.rl2) {
                    if (view.getVisibility() == View.VISIBLE) {
                        view.setVisibility(View.INVISIBLE);
                        dataItems.get(getAdapterPosition()).visible2 = false;
                    } else {
                        view.setVisibility(View.VISIBLE);
                        dataItems.get(getAdapterPosition()).visible2 = true;
                    }
                }
            }
        }
    }
    

    A few comments on the adapter:

    • Make sure not to hard-code any visibilities inside onBindViewHolder(). Use your data set instead.

    • Attach the onClickListeners inside the constructor of the ViewHolder class. If you prefer, you can add the listeners as anonymous classes and avoid the if-clauses of a unified onClick method. That's mostly a matter of taste.

    • Inside the listener(s), be careful to do both: refresh the view and update the model. This is the crucial point.

    • For your purposes, do not use notifyDataSetChanged. This is only necessary, if you add or remove DateItems from the ArrayList<DataItem> called dataItems.