에러 내용
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() }
- ViewPager2 를 사용하는 Fragment를 클래스 변수로 선언
- 해당 화면을 두 번째 진입
- 펑
두 번째
- Multiple-Start-Destination 를 적용하기 위해 위의 코드를 사용
- ViewPager2 가 있는 Fragment 를 여러 시작점 중에 하나의 시작점으로 사용
- 해당 시작점으로 전환
- 펑
재현 원인
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
함수가 한 번 돌면서
mFragmentMaxLifecycleEnforcer
는 null
이 아닌 값으로 변경된다.
이 상태에서 다른 화면에서 다시 한번 ViewPagerFragment
를 진입하게 되면
mFragmentMaxLifecycleEnforcer
는 null
이 아니라서 에러가 발생하는 것.
두 번째 경우
구글이 만들어준 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 |