본문 바로가기

안드로이드

Error ) ViewPager2 - onAttachedToRecyclerView

반응형

에러 내용

java.lang.IllegalArgumentException
at androidx.core.util.Preconditions.checkArgument(Preconditions.java:36)
at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:132)
at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1209)

viewpager2에 adapter 연결 시에 크래시 발생

재현 경로

발견한 재현 경로는 2가지다. 더 있을수도 있음

첫 번째

Activity의 클래스 변수로 선언
private var fragment : ViewPagerFragment = ViewPagerFragment()

supportFragmentManager.beginTransaction().replace(R.id.frame_layout, fragment)

by lazy 를 통해서 선언
private val adapter by lazy { ViewPager2Adapter() }
  1. ViewPager2 를 사용하는 Fragment를 클래스 변수로 선언
  2. 해당 화면을 두 번째 진입

두 번째

 

android/architecture-components-samples

Samples for Android Architecture Components. . Contribute to android/architecture-components-samples development by creating an account on GitHub.

github.com

  1. Multiple-Start-Destination 를 적용하기 위해 위의 코드를 사용
  2. ViewPager2 가 있는 Fragment 를 여러 시작점 중에 하나의 시작점으로 사용
  3. 해당 시작점으로 전환

재현 원인

FragmentStateAdapter 코드 중 일부

@CallSuper
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    checkArgument(mFragmentMaxLifecycleEnforcer == null);
    mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
    mFragmentMaxLifecycleEnforcer.register(recyclerView);
}

@CallSuper
@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
    mFragmentMaxLifecycleEnforcer.unregister(recyclerView);
    mFragmentMaxLifecycleEnforcer = null;
}

위의 코드는 FragmentStateAdapter 의 코드 중에 일부인데,

checkArgument(mFragmentMaxLifecycleEnforcer == null);

이 부분이 에러가 발생하는 부분이다.

첫 번째 경우

Fragment 를 클래스 변수로 선언하여

ViewPagerFragment와 내부의 adapter 는 이미 생성이 된 상태이며,

첫 번째 진입 시에 onAttachedToRecyclerView 함수가 한 번 돌면서

mFragmentMaxLifecycleEnforcernull 이 아닌 값으로 변경된다.

 

이 상태에서 다른 화면에서 다시 한번 ViewPagerFragment 를 진입하게 되면

mFragmentMaxLifecycleEnforcernull 이 아니라서 에러가 발생하는 것.

두 번째 경우

구글이 만들어준 Multiple-Start-Destination 코드는 설정 시에 내비게이션의 시작점이 되는 화면들을 전부

fragmentManager.beginTransaction().attach(~) 를 통해 한 번씩 로딩시킨다.

 

첫 번째 경우와 마찬가지로 onAttachedToRecyclerView 함수가 동작하게 되고,

해당 화면 진입 시 에러가 발생하는 것.

해결법

새로 만들기

X  private var fragment : ViewPagerFragment = ViewPagerFragment()

O  supportFragmentManager.beginTransaction().replace(R.id.frame_layout, ViewPagerFragment())

----------------------------------------------------------------

X  private val adapter by lazy { ViewPager2Adapter() }

O  private var adapter : ViewPager2Adapter? = null
   adapter =  ViewPager2Adapter()

fragment를 새로 생성하거나, adapter를 새로 생성하면 된다.

 

mFragmentMaxLifecycleEnforcer 값은 adapter에 존재하기 때문에
둘 중 뭘 새로 만들던 당연히 null로 들어간다.

 

대신 화면을 보존해야 한다면 아래 방법을 쓰면 된다.

mFragmentMaxLifecycleEnforcer 값 날려주기

override fun onPause() {
    super.onPause()
    dataBinding.viewPager2.adapter = null
}

onPause에서 viewPager2의 adapter를 초기화해주면 onDetachedFromRecyclerView 로직이 돌면서

mFragmentMaxLifecycleEnforcer 값이 null 로 초기화된다.

 

당연히 onResume에서 다시 adapter를 설정해줘야 앱 이탈 후에 돌아와도 원하는 동작을 한다.

잠깐?

함수명이 onAttachedToRecyclerView, onDetachedFromRecyclerView 라서
RecyclerView 는 왜 문제가 없지 싶었는데, RecyclerView 에는 함수만 존재하고 내부 구현은 없다.

반응형

'안드로이드' 카테고리의 다른 글

ViewModel - SavedStateHandle  (0) 2021.01.24
Retrofit - 세션 유지하기  (0) 2021.01.06
Hilt - @SingletonComponent  (0) 2020.11.16
Espresso intents - 카메라 촬영 테스트 하기  (0) 2020.07.17
Android - java.time 패키지  (0) 2020.07.15