FragmentManager ?
Note: We strongly recommend using the
Navigation library to manage your app's navigation. The framework follows best practices for working with fragments, the back stack, and the fragment manager.
본격적으로 시작하기 전에, fragment manager에 대한 안드로이드 공식문서를 살펴보면, 위와 같이 jetpack navigation library를 사용하는 것을 권장한다고 알리고 있다. 최근 사이드 프로젝트를 하면서 intent나 bundle에 ROOT_SCREEN을 넘겨주어 화면전환을 하는 것이 번거로워져 jetpack navigation으로의 migration을 고려하고 있는데, 이에 대해서는 추후 알아보도록 하겠다
FragmentManager
앱 프래그먼트에서 프래그먼트를 추가, 삭제 또는 교체하고 백 스택에 추가하는 등의 작업을 실행하는 클래스이다.
- 이때 getSupportFragmentManager()를 통해 FragmentManager에 접근할 수 있다
- 프래그먼트 내에서 getChildFragmentManager()를 통해 해당 프래그먼트의 하위 요소 FragmentManager에 접근할 수 있다
- 하위가 아닌 호스트(부모) FragmentManager에 접근해야 한다면 getParentFragmentManager()를 통해 가능하다
fragment는 activity에 종속적이며, 자식 fragment는 여러개 가질 수 있는데, 이런 상황에서 어떤 fragmentManager를 사용할 지 어려질 때가 있다. 공식문서에서 제공한 아래 그림을 살펴보자
여기서 눈여겨봐야할 점은, 다음과 같다 (사실 이 그림 하나로 이해가 완벽히 된다)
- FragmentActivity에서 supportFragmentManager(), Fragment에서 parentFragmentManager()가 모두 FragmentActivity의 FragmentManager
- Fragment에서 childFragmentManager(), ChildFragment에서 parentFragmentManager()가 모두 Fragment의 FragmentManager
를 가리킨다는 것을 알 수 있다
Fragment Transaction
FragmentManager는 런타임시, 프래그먼트를 추가하거나 삭제하는 등의 백스택 작업을 수행하고, 이 변경사항 집한은 FragmentTransaction으로 실행된다
private inline fun <reified T : Fragment> navigateTo() {
supportFragmentManager.commit {
replace<T>(R.id.fcv_main, T::class.simpleName)
}
}
액티비티에서 바텀네비게이션을 이용한 프래그먼트 전환을 위한 코드이다. 위와 같이 activity의 fragmentManager인 supportFragmentManager()를 활용해 replace, add, remove와 같은 다양한 동작을 수행한다
같은 코드를 해당 액티비티의 프래그먼트에서 작성하면
private inline fun <reified T : Fragment> navigateTo() {
activity?.supportFragmentManager?.commit {
replace<T>(R.id.fcv_main, T::class.simpleName).addToBackStack(ROOT_FRAGMENT_HOME)
}
}
private inline fun <reified T : Fragment> navigateTo() {
parentFragmentManager.commit {
replace<T>(R.id.fcv_main, T::class.simpleName).addToBackStack(ROOT_FRAGMENT_HOME)
}
}
위와 같이 두가지로 작성이 가능하다 Fragment가 종속되어있는 activity의 fragmentManager를 받아오는 방식과, 바로 parentFragmentManager()로 호스트 fragmentManager에 접근하는 방식 두가지 모두 가능하다
parentFragmentManager의 ISE
fragment transaction 외에도 dialog.show()의 첫번째 인자로 fragmentManager가 필요하다
평소엔 parentFragmentManager()를 주로 사용했는데, 팀원간의 코드 리뷰 중 그 내부 코드를 살펴보게 되었다
parentFragmentManager는 fragmentManager가 null인 경우 IllegalStateException을 발생시킨다는 것을 알게되었고,
런타임 시에 앱이 죽는걸 방지하기 위해 팀 안에서 두가지 방안을 고려했다
- parentFragmentManager 대신 activity?.supportFragmentManager?. 사용해 nullable타입 반환
- IllegalStateException이 발생하면 이를 try-catch문으로 예외처리하는 확장 함수 작성
private fun Fragment.safeFragmentManager(): FragmentManager? {
return try{
parentFragmentManager
} catch(e: IllegalStateException){
// Log나 Crashlytics 처리
null
}
}
activity.supportFragmentManager는 결국 parentFragmentManager와 같은 걸 가리키기에 첫번째 방법으로 정했고, 그에 따라 리팩토링 했다