개발하다보면 fragment manager를 사용해야 될 때가 거의 반드시 옵니다.
아래는 제가 이번에 프로젝트를 하면서 사용한 코드들인데요, 솔직히 말하면 지금까지 아무 생각 없이 썼습니다... 🙈 그래서... 이제는!!! 알아야 할 때가 온 것 같습니다!
예시1 (activity 내에서)
supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
supportFragmentManager.beginTransaction()
.replace(R.id.main_fl, fragment, tag)
.addToBackStack(tag)
.commitAllowingStateLoss()
예시2 (activity 내에서)
supportFragmentManager.popBackStackImmediate()
val index = supportFragmentManager.backStackEntryCount - 1
if (index >= 0) {
val backEntry = supportFragmentManager.getBackStackEntryAt(index)
changeBottomNavigationView(backEntry.name)
}
예시3 (activity 내에서)
private fun changeBottomNavigationView(name: String?) {
when (name) {
ApplicationClass.FRAGMENT_HOME_ID -> {
BottomNavigationUtil.setSelect(this, R.id.menu_main_btm_nav_home)
}
...
예시4 (util class 내에서)
fun setSelect(activity: MainActivity, @IdRes elementId: Int) {
CoroutineScope(Main).launch {
val bottomNavigationView =
activity.findViewById<BottomNavigationView>(R.id.main_btm_nav)
bottomNavigationView.menu.findItem(elementId).isChecked = true
}
}
예시5 (adapter class 내에서)
(context as MainActivity).supportFragmentManager.beginTransaction()
.add(R.id.main_fl, UserProfileFragment(item.userNo))
.addToBackStack(ApplicationClass.FRAGMENT_PROFILE_ID)
.commitAllowingStateLoss()
예시6 (fragment 내에서)
childFragmentManager.beginTransaction()
.add(R.id.container, GroupCategoryListFragment(childFragmentManager, this))
.addToBackStack(null)
.commit()
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. For more information about Navigation, see Get started with the Navigation component and Migrate to the Navigation component.
fragment manager 공식문서를 찾아보니, 위와 같은 공지글을 먼저 마주하게 되었습니다. 해석해보면, 결국 이제 fragment, 백스택, fragment manager 와 관련된 작업을 하려면 jetpack 의 navigation library 를 사용하라는 것입니다. jetpack 에 대해서는 차차 알아보도록 하겠습니다. 그전에 공부할게 많아요 다만, jetpack navigation library 를 사용할 경우, 개발자를 대신해서 fragment manager 를 사용하기 때문에 개발자가 직접 fragment manager 를 만질 일은 없다는 정도로 우선 알아두면 될 것 같습니다.
FragmentManager is the class responsible for performing actions on your app's fragments, such as adding, removing, or replacing them, and adding them to the back stack.
FragmentManger 란 앱의 fragment(s) 를 더하고, 삭제하고, 교체하고, 백스택에 더하는 활동 등을 책임을 지는 class 입니다.
Every FragmentActivity and subclasses thereof, such as AppCompatActivity, have access to the FragmentManager through the getSupportFragmentManager() method.
모든 FragmentActivity, 그리고 그것의 subclass (AppCompatActivity 와 같은) 는 getSupportFragmentManager로 FragmentManager 에 접근할 수 있습니다.
실제로 코드를 뜯어보니getSupportFragmentManager가 FragmentManager를 반환하는 FragmentActivity 클래스의 멤버 함수로 구현되어 있는 것을 확인할 수 있었습니다!
💡그런데 FragmentActivity와 Activity 의 차이가 무엇일까요?
: 이곳에 의하면 FragmentActivity 는 Activity 의 subclass 로, 안드로이드 오래된 버전과의 호환성을 보장하기 위해 추가적으로 몇 가지 메서드를 제공하지만 크게는 별 차이 없다고 합니다.
Fragments are also capable of hosting one or more child fragments. Inside a fragment, you can get a reference to the FragmentManager that manages the fragment's children through getChildFragmentManager(). If you need to access its host FragmentManager, you can use getParentFragmentManager().
프래그먼트는 하나 이상의 프래그먼트를 호스팅할 수 있다(담을 수 있다). 프래그먼트 내에서 자식 프래그먼트를 관리하는 FragmentManager 에 접근하기 위해서 getChildFragmentManager()를 사용한다. 그리고 자식 프래그먼트에서 부모의 FragmentManager 에 접근하기 위해서는 getParentFragmentManager 를 사용하면 된다.
example1의 host fragment 는 두 개의 자식 프래그먼트를 호스트하고 있고, example2의 host fragment 는 하나의 자식 프래그먼트를 호스트하고 있습니다. 프래그먼트 내에서 viewpager2를 사용하는 경우를 예로 들 수 있겠네요.
각 호스트는 자식 프래그먼트를 관리하는 FragmentManager 를 가지고 있습니다. 여기에 접근하게 되면, 유저에게 보여지는 프래그먼트를 조작/관리할 수 있겠지요.
결론적으로, FragmentManager property(FragmentManger 를 부르는 메서드)는 이것을 부르는 위치가 어디인지, 그리고 어떤 FragmentManager(자식인지 부모인지)를 부르는지에 따라서 다르다고 할 수 있다.
FragmentManager 는 프래그먼트 백스택을 관리합니다. 런타임에 유저의 상호작용에 따라서 프래그먼트를 더하고 제거하는 등의 백스택 오퍼레이션을 수행합니다. 각각의 변화(더하거나 제거하는)는 'FragmentTrasaction' 라는 하나의 단위로 commit 됩니다.
유저가 백버튼을 누르거나, FragmentManager.popBackStack() 를 호출하면 가장 위에 있는 FragmentTrasaction 이 스택에서 pop 됩니다. 즉, transaction 이 반대로 처리되는 것이지요. 예를 들어 bottom navigation A 에서 B로 이동한 다음 백버튼을 눌렀을 때, A->B transaction 이 스택에서 pop 되면서 B->A 로 작동하는 것이지요.
만약 더 이상 pop 할 프래그먼트가 없고 자식 프래그먼트가 없다면, activity 로 거슬러 올라갑니다.
addToBackStack()을 호출할 경우, 해당 transaction이 스택에 포함되는데 그 수는 무한정이 되어도 상관없습니다. 그리고 스택을 pop 했을 경우 transaction 은 reverse 되어서 처리되지요. 그런데 popBackStack 하기 직전에 수행한 transaction 을 addToBackStack 하지 않았을 경우, 해당 transaction은 반대로 처리되지 않습니다. 즉, addToBackStack 과 popBackStack 은 짝꿍이라고 볼 수 있습니다. 해당 transaction 을 pop 하고 싶다면, addToBackStack 을 반드시 수행해야 하는 것이지요.
addToBackStack 하지 않았는데 pop 할 경우는 어떻게 될까요?
프래그먼트 컨테이너에 프래그먼트를 보여주려면 FragmentManager 를 사용해서 FragmentTransaction 를 만들면 됩니다. 이를 사용해서 컨테이너에 프래그먼트를 더하거나 교체해서 보여주면 됩니다.
간단한 FragmentTransaction 예시입니다.
supportFragmentManager.commit {
replace<ExampleFragment>(R.id.fragment_container)
setReorderingAllowed(true)
addToBackStack("name") // name can be null
}
이 예시에서는 ExampleFragment 가 fragment_container 라는 id 를 가진 프래그먼트를 교체(replace)하고 있습니다. replace 메서드에 ExampleFragment 클래스를 전달하면, FragmentManager 가 FragmentFactory 를 이용해서 인스턴스화 합니다.
setReorderingAllowed(true) 는 해당 프래그먼트의 상태 변화를 최적화해서 애니메이션과 transaction 이 잘 작동하도록 합니다.
addToBackStack() 는 앞서 얘기했던 것 처럼 pop 할 수 있도록 합니다. 만약 하나의 transaction 에 여러 프래그먼트를 더하거나 제거하거나 하면, 해당 transaction 이 pop 되었을 때 모두 취소되게 됩니다!
또한 remove 하는 transaction 을 수행할 때 addToBackStack을 부르지 않으면 해당 프래그먼트는 destroy 되고 유저는 해당 프래그먼트로 돌아갈 수 없습니다. 하지만 addToBackStack을 부르면, 프래그먼트는 stop 상태가 되고 돌아갈 수 있습니다. 이때, view 는 destory 된답니다. (이 부분은 Fragment lifecycle 을 참고해주세요.)
addToBackStack에 String 으로 특정 값을 넘기면 pop 할 때도 특정 값을 넣어서 해당 transaction 을 pop 할 수 있습니다.
지금까지 FragmentManager 가 어떤 역할을 하는지, 어디서 어떻게 접근하면 되는지에 대해서 알아보았습니다. 다음 포스트에서는 자주 사용되는 FragmentManager 의 메서드들을 살펴보고 비교 분석해보도록 하겠습니다! 감사합니다~!😉