마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

리사이클러뷰안에 리사이클러뷰 구현

0 추천

디버깅을 해봤는데 왜 nullpointer에러가 뜨는지 모르겠습니다..

java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.recyclerview.widget.RecyclerView.setLayoutManager(androidx.recyclerview.widget.RecyclerView$LayoutManager)' on a null object reference
        at com.mango.wellbeingdiary.main.adapter.RegisteredHealthLogAdapter.onBindViewHolder(RegisteredHealthLogAdapter.java:78)
        at com.mango.wellbeingdiary.main.adapter.RegisteredHealthLogAdapter.onBindViewHolder(RegisteredHealthLogAdapter.java:21)

[상위 리사이클러뷰]

public class RegisteredHealthLogAdapter extends RecyclerView.Adapter<RegisteredHealthLogAdapter.ViewHolder>{
    ArrayList<String> registeredLogs = new ArrayList<>();
    RecyclerView recyclerView;
    TextView textView;
    Button button;

    public class ViewHolder extends RecyclerView.ViewHolder {

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

            textView = itemView.findViewById(R.id.registeredTextView);
            button = itemView.findViewById(R.id.addWeight);
            recyclerView = itemView.findViewById(R.id.todayLogRectyclerView);
        }

        public TextView getTextView() {
            return textView;
        }

        public Button getButton() { return button; }

        public RecyclerView getRecyclerView() { return recyclerView; }
    }

    public RegisteredHealthLogAdapter(ArrayList<String> list) {
        registeredLogs = list;
    }

    @NonNull
    @Override
    public RegisteredHealthLogAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = null;

        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.health_registered_add, parent, false);

        return new RegisteredHealthLogAdapter.ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull RegisteredHealthLogAdapter.ViewHolder holder, int position) {
        String item = registeredLogs.get(position);
        holder.getTextView().setText(item);

        // 아이템 추가
        ArrayList<setList> setLists = new ArrayList<>();
        setList list = new setList("SET", "0", "0");
        setLists.add(list);


        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(holder.itemView.getContext());
        holder.getRecyclerView().setLayoutManager(linearLayoutManager);
        HealthLogSetAdapter adapter = new HealthLogSetAdapter(setLists);

        recyclerView.setAdapter(adapter);

        holder.getButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 추가하기 버튼이 클릭되면
                Toast.makeText(v.getContext(), "클릭됨", Toast.LENGTH_LONG).show();
            }
        });
    }

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

    public void addItem(String data) {
        registeredLogs.add(data);
    }
}

 

[하위 리사이클러뷰]

public class HealthLogSetAdapter extends RecyclerView.Adapter<HealthLogSetAdapter.ViewHolder>{
    ArrayList<com.mango.wellbeingdiary.main.health.list.setList> setList = new ArrayList<>();
    TextView setTextView, weightTextView, repsTextView;

    public class ViewHolder extends RecyclerView.ViewHolder{
        public ViewHolder(@NonNull View itemView) {

            super(itemView);

            setTextView = itemView.findViewById(R.id.set);
            weightTextView = itemView.findViewById(R.id.healthWeight);
            repsTextView = itemView.findViewById(R.id.reps);
        }

        public TextView getSetTextView() { return setTextView; }

        public TextView getWeightTextView() { return weightTextView; }

        public TextView getRepsTextView() { return repsTextView; }
    }

    public HealthLogSetAdapter(ArrayList<setList> list) {
        setList = list;
    }

    @NonNull
    @Override
    public HealthLogSetAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = null;

        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.health_log_add_set, parent, false);

        return new HealthLogSetAdapter.ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(@NonNull HealthLogSetAdapter.ViewHolder holder, int position) {
        setList item = setList.get(position);
        holder.getSetTextView().setText(item.getSetsText());
        holder.getSetTextView().setText(item.getWeightText());
        holder.getSetTextView().setText(item.getRepsText());
    }

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

}
매력적인수박 (670 포인트) 님이 2021년 7월 23일 질문
코드에는 LinearLayout이 안보이네요.

2개의 답변

0 추천
 
채택된 답변

 Nested RecyclerView에 대한 레퍼런스가 잘못 된 것 같아 보이네요.

public class RegisteredHealthLogAdapter extends RecyclerView.Adapter<RegisteredHealthLogAdapter.ViewHolder>{
    RecyclerView recyclerView;  // 에러
    TextView textView;  // 에러
    Button button;  // 에러
 
    public class ViewHolder extends RecyclerView.ViewHolder {
 
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
 
            textView = itemView.findViewById(R.id.registeredTextView);
            button = itemView.findViewById(R.id.addWeight);
            recyclerView = itemView.findViewById(R.id.todayLogRectyclerView);
        }
 
        ...
        public RecyclerView getRecyclerView() { return recyclerView; } 
    }
   ...
}

 

아래 코드와 비교해 보세요.
 

public class RegisteredHealthLogAdapter extends RecyclerView.Adapter<RegisteredHealthLogAdapter.ViewHolder>{
    public class ViewHolder extends RecyclerView.ViewHolder {
        private RecyclerView recyclerView;
        private TextView textView;
        private Button button;
  
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
 
            textView = itemView.findViewById(R.id.registeredTextView);
            button = itemView.findViewById(R.id.addWeight);
            recyclerView = itemView.findViewById(R.id.todayLogRectyclerView);
        }
 
        ...
        public RecyclerView getRecyclerView() { return recyclerView; } 
    }
   ...
}

문제는 님이 상위 어댑터에서 onBindViewHolder를 할 때 각각의 아이템이 리사이클러뷰를 가지고 있는데, 상위어댑터에 recyclerView 변수를 공유함으로써 뷰홀더가 바인딩이 될 때마다 이 recyclerView 변수가 리셋되기 때문에 Null이 된 것으로 보입니다. 

아랫 처럼 Nested Class를 밖으로 빼보면 명확해 보입니다.

public class RegisteredHealthLogAdapter extends RecyclerView.Adapter<RegisteredHealthLogAdapter.ViewHolder>{
    ArrayList<String> registeredLogs = new ArrayList<>();
    RecyclerView recyclerView;
    TextView textView;
    Button button;
   ...
}

public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
 
            textView = itemView.findViewById(R.id.registeredTextView);
            button = itemView.findViewById(R.id.addWeight);
            recyclerView = itemView.findViewById(R.id.todayLogRectyclerView);
        }
 
        public TextView getTextView() {
            return textView;
        }
 
        public Button getButton() { return button; }
 
        public RecyclerView getRecyclerView() { return recyclerView; }
    }
}

님의 경우는 똑같은 실수를 방지하기 위해 가능하면 Nested Class를 당분간 사용하지 마시고 별도의 클래스로 분리해서 작업하시는 것이 좀 더 안전할 것 같습니다.

spark (228,990 포인트) 님이 2021년 7월 23일 답변
매력적인수박님이 2021년 7월 23일 채택됨
답변 감사합니다.

spark님이 피드백 주신 것처럼 코드를 수정했음에도 같은 에러가 발생하네요..
디버그 모드로 확인해봤는데 에러가 나오는 부분(holder.getRecyclerView().setLayoutManager) )가 null이 아닌데 왜 nullpointer가 발생할까요...
수정하신 부분을 다시 올려보세요.
null object reference 추가질문
0 추천

참고로 viewHolder에 데이터을 바인딩하는 부분은 ViewHolder 에 위임하는 것이 더 안전합니다. 그래야 ViewHolder를 변경할 때 adapter를 손댈 일이 덜 생깁니다.

    @Override
    public void onBindViewHolder(@NonNull RegisteredHealthLogAdapter.ViewHolder holder, int position) {
        ...
 
       holder.bindItems(list);
    }


public class RegisteredHealthLogAdateer.ViewHolder... {
    private final HealthLogSetAdapter adapter;
   
    public RegisteredHealthLogAdateer(View itemView, View.OnClickListener buttonClickListener) {
        super(itemView);

        // find views

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(itemView.getContext());
        recyclerView.setLayoutManager(linearLayoutManager);
        adapter = new HealthLogSetAdapter();
        recyclerView.setAdapter(adapter);    
        button.settOnClickListener(buttonClickListener);
    }

    public void bindItems(items: List<setList>) { 
        adapter.setItems(items); 
        adapter.notifyDatasetChanged();
    }
}

 

그리고 Nested RecyclerView는 부모와 자식 어댑터 간에 뷰를 재사용하도록 할 필요가 있다면 View pool을 사용하는 것이 좋습니다.
 

private RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();

...

parentViewHolder
            .childRecyclerView
            .setRecycledViewPool(viewPool);

 

spark (228,990 포인트) 님이 2021년 7월 23일 답변
spark님이 2021년 7월 23일 수정
...