ybybyb
ybybyb

Reputation: 1749

Nested RecyclerView's notifyDataSetChanged() is not working

I am implementing a Nested Recycler View.

The nested recycler view is dynamically added for each item when the button is clicked.

I have implemented this function. But when you click the button to add an item,

the item update is only possible through the nested recyclerview's setAdapter(),

not the notifyDataSetChanged()

Why is this happening? Why isn't it updated with notifyDataSetChanged()?

At least as far as I know the setAdapter() should only be called once.

Does it matter if the setAdapter() is called multiple times?

What I want is to be updated by the notifyDataSetChanged(), not an setAdapter().

But item is updated by the setAdapter().

Why?

RoutineModel.java

public class RoutineModel {
    String routine;
    public ArrayList<RoutineDetailModel> arrayListDetail;

    public RoutineModel(String routine) {
        this.routine = routine;
    }

    public String getRoutine() {
        return routine;
    }
}

RoutineAdapter.java (Nested Recyclerview's Adapter)

public class RoutineAdapter extends RecyclerView.Adapter<RoutineAdapter.ViewHolder> {
    ArrayList<RoutineModel> routineItems = new ArrayList<>();
    Context context;

    public void addItem(RoutineModel item) {
        routineItems.add(item);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View itemView =  inflater.inflate(R.layout.routine_item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        RoutineModel curRoutineItem = routineItems.get(position);
        holder.setItems(curRoutineItem);

        // Set Routine Detail (Nested RecyclerView)
        holder.setRoutineDetailRecyClerView();
        RoutineDetailAdapter detailAdapter = new RoutineDetailAdapter();
        curRoutineItem.arrayListDetail = new ArrayList<>();
        curRoutineItem.arrayListDetail.add(new RoutineDetailModel());

        holder.routine_detail.setAdapter(detailAdapter);
        detailAdapter.addItems(curRoutineItem.arrayListDetail);

        // add NestedRecyclerView's item (Dynamically)
        holder.addSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                curRoutineItem.arrayListDetail.add(new RoutineDetailModel());
                detailAdapter.addItems(curRoutineItem.arrayListDetail);
                holder.routine_detail.setAdapter(detailAdapter); // HERE!!!!!
            }
        });
    }

    @Override
    public int getItemCount() {
        return routineItems.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder  {
        TextView routine;
        Button addSet;
        RecyclerView routine_detail;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            initViews();
        }

        private void initViews() {
            routine = itemView.findViewById(R.id.routine);
            routine_detail = itemView.findViewById(R.id.detail_routine);
            addSet = itemView.findViewById(R.id.add_set);

        }
        private void setItems(RoutineModel routineItem) {
            routine.setText(routineItem.getRoutine());
        }
        public void setRoutineDetailRecyClerView() {
            routine_detail.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
            routine_detail.setHasFixedSize(true);
        }
    }
}

RoutineDetailModel.java (Nested Recyclerview's Model)

public class RoutineDetailModel {
    String set;
    String weight;
    String reps;
}

RoutineDetailAdapter.java

public class RoutineDetailAdapter extends  RecyclerView.Adapter<RoutineDetailAdapter.ViewHolder>{
    ArrayList<RoutineDetailModel> items = new ArrayList<>();

    public void addItems(ArrayList<RoutineDetailModel> items) {
        this.items = items;
        notifyDataSetChanged();
    }

    public ArrayList<RoutineDetailModel> getItem() {
        return this.items;
    }


    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View itemView = inflater.inflate(R.layout.routine_detail_item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        RoutineDetailModel item = items.get(position);
        holder.setItem(item);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView set;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            set = itemView.findViewById(R.id.set);
        }

        private void setItem(RoutineDetailModel item) {
            set.setText("TEST");
        }
    }
}

++++++++++++++++++++++++++++++++++

OnCreateViewHolder

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View itemView =  inflater.inflate(R.layout.routine_item, parent, false);
        ViewHolder holder = new ViewHolder(itemView);
        detailAdapter = new RoutineDetailAdapter();
        holder.setRoutineDetailRecyClerView();
        holder.routine_detail.setAdapter(detailAdapter);

        return holder;
    }

Upvotes: 1

Views: 1186

Answers (1)

cactustictacs
cactustictacs

Reputation: 19562

RecyclerViews work by recycling a handful of ViewHolder objects, and updating them to display a different item's data. That happens in onBindViewHolder, which is where you're setting it up to display the data with your holder.setItems(curRoutineItem) call, right?

But each time you bind a view holder, this is what you're doing:

  • getting the RoutineModel for the current position
  • clearing its arrayListDetail and adding a single, new ``RoutineDetailModel```
  • creating a new RoutineDetailAdapter which only contains that single RoutineDetailModel, and setting that on the ViewHolder's RecyclerView, replacing what was already there
  • adding a click listener that adds another RoutineDetailModel to the RoutineModel and adapter (both of which will be cleared and recreated next time onBindViewHolder is called)

So I don't know specifically where your problem lies, but you're not actually storing this state with the added items - it gets wiped every time onBindViewHolder is called, whenever an item scrolls into view.

If you're saying that you click the button and nothing happens in the RecyclerView without calling setAdapter again, then I'm not sure why that would happen - at that point detailAdapter is referencing the adapter in the recycler view, and you are adding another item (technically replacing its items with the same list of one plus another one).

But if you mean you scroll the list and your items disappear, yeah that's going to happen, because you delete them every time the item scrolls back into view. This stuff looks like data initialisation:

curRoutineItem.arrayListDetail = new ArrayList<>();
curRoutineItem.arrayListDetail.add(new RoutineDetailModel());

and definitely shouldn't be happening in onBindViewHolder. This stuff:

RoutineDetailAdapter detailAdapter = new RoutineDetailAdapter();
holder.routine_detail.setAdapter(detailAdapter);

is RecyclerView initialisation and should probably be happening once per recyclerview, in onViewHolderCreated. Then in onBindViewHolder you can just call getAdapter and set the items you want to display.

That might help you with your issue, I'm not sure what you're seeing exactly because I'd expect it to feel pretty broken as it is!

Upvotes: 2

Related Questions