Android프로그래밍

[Android]안드로이드 RecyclerView에 View Binding 사용시 오류 해결 방법: java.lang.NullPointerException: Attempt to read from field ‘androidx.recyclerview.widget.RecyclerView com.test.databinding.FragmentHomeBinding.recyclerView’ on a null object reference in method

리사이클러뷰를 뷰 바인딩 아키텍쳐를 사용하여 코딩하였는데, 간할적 혹은 주기적으로 발생한 오류이다.

java.lang.NullPointerException: Attempt to read from field ‘androidx.recyclerview.widget.RecyclerView com.test.databinding.FragmentHomeBinding.recyclerView’ on a null object reference in method

오류가 발생한 코드는 다음과 같다.


public class HomeFragment extends Fragment implements RequestApi.OnResultListener {

    private FragmentHomeBinding binding;
    protected RequestApi requestApi;
    AlertDialog.Builder alertDialogBuilder = null;

    public RecyclerView.LayoutManager layoutManager;
    private HomeStockRecyclerViewAdapter adapter;

    private ArrayList<UserStockVo> stockItemList;

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        HomeViewModel homeViewModel =
                new ViewModelProvider(this).get(HomeViewModel.class);

        binding = FragmentHomeBinding.inflate(inflater, container, false);
        View root = binding.getRoot();


        final TextView baseDate = binding.baseDateText;
        baseDate.setText("주가 기준일: 2024.12.20");

        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
        binding.recyclerView.setLayoutManager(layoutManager);
//        binding.recyclerView.setHasFixedSize(true);

        requestApi = ((StockProfitManageApplication) getContext().getApplicationContext()).getRequestApiManager();
        requestApi.addListener(HomeFragment.this); //(RequestApi.OnResultListener)

        searchUserStockInfo("", "");


//        final Button addbtn = binding.addBtn;
//        homeViewModel.getText().observe(getViewLifecycleOwner(), addbtn::setText);
        return root;
    }

.....이상 생략

Log.d(BaseSettings.TAG, "####  stockItemList.size :" + stockItemList.size());
if(stockItemList!=null && stockItemList.size() > 0){
    adapter = new HomeStockRecyclerViewAdapter(stockItemList, getActivity(), false);

    binding.recyclerView.setAdapter(adapter);

}


데이터는 정상적으로 API서버에서 받아왔지만

binding.recyclerView.setAdapter(adapter);” 코드에서 오류가 발생되는게 아닌가??


오류원인

이 오류는 binding 객체가 null인데 binding.recyclerView를 접근하려고 했기 때문에 발생한다. 해당 오류는 주로 다음 두 가지 상황에서 발생할 수 있다:

  1. binding 객체 초기화 실패: FragmentHomeBinding.inflate(inflater, container, false)가 제대로 실행되지 않았거나 반환된 binding 객체가 null이다.
  2. onDestroyView() 호출 후 onSuccess() 실행: Fragment의 생명주기에서 onDestroyView() 호출 시 binding = null;로 설정되기 때문에 이후에 onSuccess() 메서드가 호출되면 binding을 사용할 수 없다.


오류 해결방법

1. binding 초기화 확인

onCreateView() 메서드에서 FragmentHomeBinding 객체를 초기화하는 부분을 확인한다.

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    binding = FragmentHomeBinding.inflate(inflater, container, false);
    return binding.getRoot();
}

bindingnull이라면, XML 파일 이름과 데이터 바인딩 클래스를 다시 확인한다.

예: XML 파일 이름이 fragment_home.xml이라면 데이터 바인딩 클래스는 FragmentHomeBinding이다.


2. binding 사용 시점 확인

onDestroyView()에서 binding = null;을 설정한 이후, onSuccess()에서 binding.recyclerView를 사용하려 하면 문제가 발생한다. Fragment의 생명주기와 비동기 요청 처리 타이밍을 조정해야 한다.

수정 방법:

  • onSuccess() 호출 전에 bindingnull인지 확인한다.
@Override
public void onSuccess(ResponseObject vo) {
    if (binding == null) {
        Log.d(BaseSettings.TAG, "Binding is null. Skipping onSuccess.");
        return;
    }

    if (vo != null) {
        ULog.d(BaseSettings.TAG, ">>>>>>>>>> onSuccess jsonStr: " + vo.getResponseData());
        if (vo.getResponseCode() == ResponseObject.SUCCESS) {
            switch (vo.getRequestIdentyCode()) {
                case HttpInterface.REQUEST_USER_STOCK:
                    if (vo.getResponseData() != null) {
                        CustomToast.callToastFrag(getActivity(), vo.getResponseMessage());

                        Type listType = new TypeToken<List<UserStockVo>>() {}.getType();
                        stockItemList = new Gson().fromJson(vo.getResponseData().toString(), listType);

                        if (stockItemList != null && stockItemList.size() > 0) {
                            adapter = new HomeStockRecyclerViewAdapter(stockItemList, getActivity(), false);
                        } else {
                            stockItemList = new ArrayList<>();
                            stockItemList.add(new UserStockVo("", "", "", "", "", "", "", "", "", "", ""));
                            adapter = new HomeStockRecyclerViewAdapter(stockItemList, getActivity(), true);
                        }

                        binding.recyclerView.setAdapter(adapter);
                        adapter.notifyDataSetChanged();
                    }
                    break;
            }
        } else {
            alertMsgDialog(vo.getResponseMessage());
        }
    }
}


3. 비동기 요청 취소 처리

비동기 요청이 완료되기 전에 Fragment가 제거되거나 onDestroyView()가 호출될 수 있다. 이런 경우 요청을 안전하게 취소하거나 onSuccess() 호출을 막아야 한다.

onDestroyView()에서 리스너를 제거한다!

@Override
public void onDestroyView() {
    super.onDestroyView();
    if (requestApi != null) {
        requestApi.removeListener(this);
    }
    binding = null;
}


4. onDestroyViewonSuccess 타이밍 처리

안전한 리스너 등록 및 제거를 위해, onCreateView에서 리스너를 등록하고 onDestroyView에서 제거한다.

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    binding = FragmentHomeBinding.inflate(inflater, container, false);
    requestApi = ((StockProfitManageApplication) requireContext().getApplicationContext()).getRequestApiManager();
    requestApi.addListener(this); // 리스너 등록
    searchUserStockInfo("", "");
    return binding.getRoot();
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    if (requestApi != null) {
        requestApi.removeListener(this); // 리스너 제거
    }
    binding = null; // binding 해제
}

@Override
public void onSuccess(ResponseObject vo) {
    if (binding == null) {
        Log.d(BaseSettings.TAG, "Binding is null. Skipping onSuccess.");
        return;
    }
    // onSuccess 코드...
}


요약

  1. binding 초기화 상태를 확인하고, null 검사 후 사용한다.
  2. onDestroyView()에서 binding 해제 후 비동기 요청에 대한 리스너를 제거한다.
  3. 비동기 요청 처리 시점과 Fragment 생명주기를 조율한다.

오류 해결을 위해 ChatGPT를 이용하였는데, 너무나 좋구나!! 웹 검색에서 낭비하는 시간에 하나라도 더 코딩할 수 있게 되었다. 오류 해결을 위한 스트레스도 그만큼 줄어든것이다!! 개발하기 정말 편한 세상이 되었네. 고맙다 쳇GPT

    error: Content is protected !!