RecyclerView가 화면에 출력되지 않는 이유

이지훈·2023년 12월 27일
0

사진: UnsplashSigmund

서두

최근에 진행한 프로젝트들을 대부분 Jetpack Compose를 사용해서 개발하였다. 이러다간 XML을 잊어버리고 말 것 같아서, only XML을 이용한 이끔이라는 프로젝트를 진행하고 있다. 오랜만에 XML을 통해 개발을 하다보니, 상당히 애를 먹고 있는데 그중 최근에 있었던 이슈를 공유해보고자 한다.

TL;DR

setHasFixedSize(true) 를 설정 했다면 RecyclerView 의 크기(높이)를 의심 해볼 것!

문제 발생

Paging3 라이브러리를 이용한 Paging RecyclerView 를 구현하던 중, RecyclerView 가 화면에 출력이 되지 않는 문제에 직면하였다.

사실... 이는 Android 개발자라면 누구나 겪어본 문제이기에(LazyColumn 에 비해 참 신경 쓸 것이 많다.), 체크리스트를 통해 하나씩 확인하면서 누락된 조건이 있는지 확인해보면 해결이 될 줄 알았다.

1. Adapter 를 RecyclerView에 부착하였는가?

-> Yes

  private val searchCafeAdapter by lazy {
    SearchCafeAdapter(
      object : SearchCafeClickListener {
        override fun onItemClick(position: Int) {
          Toast.makeText(requireContext(), "${position}번째 장소를 저장했습니다.", Toast.LENGTH_SHORT).show()
        }
      },
    )
  }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    initView()
    initObserver()
  }

  private fun initView() {
    binding.rvSearchCafe.apply {
      setHasFixedSize(true)
      addDivider(R.color.gray_300)
      adapter = searchCafeAdapter
    }
  }

2. LayoutManager를 설정하였는가?

-> Yes

  <androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_search_cafe"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="16dp"
    android:layout_marginTop="8dp"
    android:orientation="vertical"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/cv_search_cafe"
    tools:listitem="@layout/item_search_cafe_location"/>

LayoutManager 는 이처럼 RecyclerView 내에 혹은, fragment에서 직접 코드(위에 initView() 함수내에 정의해주면 될 것 같다.)로도 설정할 수 있다.
이것은 취향 차이이며, 나는 되도록 xml 에서 가능하다면, xml에서 설정하는 것을 선호하는 편이다. 뷰의 코드가 깔끔해지는 것도 있고, 아마 DataBinding 을 쓸 적에 습관이 남아있는 듯 하다.

3. submitData 함수를 호출해주었는가? (API 가 정상적으로 호출되는가?)

-> Yes

API 도 정상적으로 호출되는 것을 로그를 통해 확인할 수 있었다.(로그는 생략)

viewModel

  val searchPlaceList: Flow<PagingData<PlaceEntity>> = getPlaceListUseCase(query).cachedIn(viewModelScope)

Fragment

  private fun initObserver() {
    repeatOnStarted {
      launch {
        viewModel.searchPlaceList.collectLatest {
          searchCafeAdapter.submitData(it)
        }
      }
      ...
   }
 }

4. 그럼 왜 안됨?

몰?루

문제 해결

문제는 RecyclerView 의 크기(높이)를 동적으로 설정해줬기 때문이다.

예전에 아래의 글을 본적이 있다.(보고 까먹었다.)
setHasFixedSize(true) 의 정체

기억나는 것은 setHasFixedSize(true)를 지정해주면, 아이템의 크기가 일정하다는 것을 명시해주어 모든 이를 랜더링할때 성능의 이득을 볼 수 있다는 것 뿐이었다. 동일한 아이템을 사용하기에 적어주는 것이 좋다고 생각하여 기재해주었다.

하지만 setHasFixedSize를 true 로 설정할 경우 RecyclerView 의 높이를 wrap_content 로 설정할 경우 데이터가 들어오기 이전의 높이로 고정이 되는데, 데이터를 현재 네트워크 통신을 통해 비동기로 받아오기 때문에, 초기상태의 RecyclerView 에는 데이터가 존재하지 않아, 높이가 0이다.

0으로 고정이 되어버리기 때문에, 이후 높이가 변화되지 않고, 데이터가 들어와서 아이템들이 채워지더라도 화면에 보여지지 않게 되는 것이다!

따라서 RecyclerView의 높이를 match_parent 또는 bottom chain 을 걸어줘야 한다.

이와 같이 수정해주면

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_search_cafe"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginHorizontal="16dp"
    android:layout_marginTop="8dp"
    android:orientation="vertical"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/cv_search_cafe"
    tools:listitem="@layout/item_search_cafe_location"/>

정상적으로 리스트가 출력되는 것을 확인할 수 있다!

반복적으로 코드를 작성하게 될 경우, 무의식적으로 코드의 의미를 생각하지 않은 채, 복붙을 하게 되는데, 코드의 의미를 생각하면서 작성하도록 하자.

끝!

참고)

https://velog.io/@haero_kim/Android-setHasFixedSizetrue-%EC%9D%98-%EC%A0%95%EC%B2%B4

https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView

profile
실력은 고통의 총합이다. Android Developer

0개의 댓글