Android/학습

[Android] Fragment Manager, Basic 편

한때미 2025. 2. 16. 07:23

 

일반적으로 운영 애플리케이션 담당 업무를 하게 되면,

페이지 이동 같은 작업은 기존에 설계되어 있는 방식 따르면 되었기 때문에

이 부분에 대해 신경을 잘 쓰지 않게 되었고, 따로 공부할 계기 및 우선순위가 계속 늦춰지고 있었다.

 

하지만, 처음부터 완벽하게 만들어지면 좋겠지만 그렇지 않은 경우는 늘 존재하고

우리 프로젝트도 이 부분에 대한버그 픽스와 리펙토링이라는 큰 백로그가 만들어지게 되었다.

 

현재 프로젝트에서는 FragmentManager를 관리하여 페이지 이동을 담당하는 Object Class가 존재하였고 엄연히 Activity의 생명주기에 의존적일 수밖에 없는 FragmentManager를 Object로 어디서든 접근하여 안전장치 없이 사용되는 로직은 실제로 운영에서도 Exception을 많이 일으키고 있었다.

 

또한, 초기 명확한 기획과 정확한 이해 없이 만들어진 class는 의도와는 다르게 동작하는 부분이 종종 있었고 이 부분을 개선하기 위해

대장정의 리펙토링에 홀로 총대 매고 달려들게 되었다.

 

사이드 프로젝트에서는 주로 Jectpack Navigation 만 사용했던지라 학습의 계기는 명확했다

 


FragmentManager

App Fragment에서 Fragment를 추가, 삭제 또는 교체하고 백 스택에 추가하는 등의 작업을 실행하는 클래스

 

Jetpack Navigation 라이브러리를 사용하는 경우, 개발자를 대신해 이 라이브러리가 FragmentManager를 사용하기 때문에 FragmentManager와의 직접적인 상호작용은 거의 필요하지 않는다.

 

 


FragmentManager 액세스

Fragment Manager는 ActivityFragment에서 액세스 할 수 있다.

Android에서 정한 [화면] 단위에서 접근할 수 있는 것이다.

 

FragmentActivity 및 그 서브클래스(예: AppCompatActivity)는 getSupportFragmentManager() 메서드를 통해 FragmentManager에 액세스 할 수 있다.

 

우리가 흔히 사용하는 Activity는 AppCompatActivity()인데, 해당 계층 구조에는 FragmentActivity가 존재한다.

 

Activity의 계층 구조

 

FragmentActivity에서 getSupportFragmentManager()가 구현되어 있을 뿐 아니라 Fragment에 대한 전반적인 컨트롤러인 FragmentController가 선언되어 있다.

 

FragmentActivity의 getSupportFragmentManager()

 

FragmentActivity의 FragmentController

 

 

Fragment는 하나 이상의 하위 프래그먼트(Fragment)를 호스팅 할 수 있다.

 

이 하위 프래그먼트들을 관리하기 위해서는 Fragment에서 getChildFragmentManager()를 통해 해당 하위 프래그먼트들을 관리할 수 있는 FragmentManager 참조를 가져올 수 있다.

 

만약, 호스트 FragmentManager에 액세스 해야 할 경우는 getParentFragmentManager()를 통해 상위 FragmentManager에 접근할 수 있다.

 

 

해당 관계를 예를 들어 그림으로 표현하여 설명하면 다음과 같다.

 

그림1. 프래그먼트와 프래그먼트의 호스트 활동 간 관계를 보여 주는 UI 레이아웃의 두 가지 예

 

해당 두 예시에서는 BottomNavigationView을 통해 최상위 탐색을 사용자에게 표시하는

하나의 Acrivity가 존재하며,

 

해당 Host AcrivityHost Fragment를 BottomNavigationView를 통해 다양한 화면으로 교체하는 작업을 수행한다.

 

다양한 화면, 즉 각 화면은 별도의 프래그먼트로 구현된다.

 

 

예를 들어,

 

Example 1에서는

Host Fragment는 분할 뷰 화면을 구성하는 하위 프래그먼트 두 개를 호스팅 하며,

(별도의 child 프래그먼트 2개)

 

Example 2에서는

Host Fragment는 ViewPager2와 같은 스와이프 뷰의 디스플레이 프래그먼트를 구성하는 하위 프래그먼트 한 개를 호스팅 한다.

(별도의 child 프래그먼트 1개)

 

 

 

해당 상태를 간단하게 도식화해서 보면 다음과 같다

그림1에 대한 구조 도식화

 

 

호스트 하는 주체인 Fragment(및 Activity)는 FragmentManager에 접근할 수 있고,

해당 FragmentManger는 하위 프래그먼트들을 관리하고 있다고 생각할 수 있다.

 

그렇다면 해당 구조에서 앞에서 말한 FragmentManager 액세스 함수에 대한 구조는 다음과 같다.

 

그림 2. Fragment 하위 구조에 따른 FragmentManager 구조

 

Host Fragment에서 parentFragmentManger는 Host ActivityFragmentManager에 접근하는 것이고,

childFragmentManager는 자신이 호스팅 하고 있는 하위 프래그먼트를 관리할 수 있는 Host FragmentFragmentManager에 접근하는 것이다.

 

 

개발자는 해당 함수를 통해 가져온 참조로 FragmentManger를 사용하여 사용자한테 표시되는 Fragment를 조작할 수 있다.

 

 


 

일반적으로 요즘 앱들은 SAA(Single Activity Architecture) 기법 등으로 단일 Activity나 소수의 Activity로 구성된다.

그렇기 때문에, 각 Acrivity는 관련 화면의 그룹을 나타낸다고 할 수 있다.

 

Activity는 최상위 탐색(navigation)을 배치할 지점과

viewModel, 다른 fragment 간의 View 상태의 범위를 지정할 수 있는 곳이다.

fragment는 앱의 개별 목적지를 나타낸다.

 

즉, 사용자가 볼 마지막 페이지 위치와 viewModel, fragment 생애주기 등을 관리한다는 말이다.

 

 

그림 1과 같이 여러 프래그먼트를 한 번에 표시(ex: 분할 뷰 or 대시보드) 하기 위해,

해당 Fragment와 childeFragmentManager를 사용하여 하위 프래그먼트를 사용할 수 있다.

 

 

Fragment 안의 Fragment, 하위 프래그먼트 다음과 같은 사용 방식으로 사용할 수 있다.

  • ViewPager2와 같은 화면 슬라이드를 이용하여 상위 Fragment에서 하위 프래그먼트를 관리하는 방식
  • 관련 화면들의 하위 탐색(Sub-navigation) 사용
  • Jetpack Navigation
    • 하위 프래그먼트들을 개별적으로 관리한다
    • Activity는 단일 상위 NavHostFragment를 호스팅 하고, 그 공간을 개별적으로 관리되는 하위 프래그먼트로 채운다.

 


 

 

FragmentManager는 Fragment 백 스택을 관리하며 런타임에 fragment를 추가하거나 삭제하는 등 백 스택 작업을 실행합니다.

 

각 변경사항 집합은 FragmentTransaction이라는 단일 단위로 함께 커밋됩니다.

 

즉, FragmentManager는 FragmentTransaction이라는 작업 단위를 가지고 있는 것이다.

 

 

그렇기 때문에 사용자가 기기에서 뒤로 버튼을 누를 경우나 개발자가 Fragmentmanager.PopBackStack()을 호출하는 경우, 최상위 FragmentTransaction이 Stack에서 사라진다.

 

하나의 작업이 pop 되면서 '뒤로 가는 동작'을 수행되는 것이다.

 

현재하고 있는 fragmentManager 관리 class 리펙토링 사유 중 가장 큰 문제도 해당 transaction이 여러 개로 분할되어 있는데, 해당 문제 때문에 하나의 작업으로 이루어져야 하는 작업이 분할되어 화면이 겹치게 보이는 등 여러 문제가 발생할 수 있었다.

 

 

Stack에 더 이상 pop 할 fragmentTransaction이 없고 개발자가 하위 프래그먼트를 사용하지 않는 경우 '뒤로' 이벤트는 Activty까지 전달(버블링) 된다.

 

 

 

fragmentTransaction은 단일 원자 작업으로, 하나의 작업단위로 취소된다. 하지만 addToBackStack()를 사용(fragment를 backStack에 쌓는 작업) 하지 않은 작업은 취소되지 않는다.

그렇기 때문에 FragmentTransaction 내에서 백 스택에 영향을 미치는 트랜잭션과 영향을 미치지 않는 트랜잭션을 같이 작업하지(인터리빙) 않아야 한다.

 

 

리펙토링 타깃 코드를 살펴보며 슬픔에 빠진다.

 

그렇다, addToBackStack()를 사용하면 따로 onBackPressed를 구현하지 않아도 해당 commit을 돌려놓는 식으로 처리가 가능한 것이다.

 

멀리 나와버린 우리 프로젝트,,,

 

 

 

addToBackStack()을 호출하면 해당 트랜젝션이 백 스택에 쌓인다.

 

addToBackStackd을 이용한다는 가정하에 정석대로라면,

화면 fragment들이 backStack에 쌓여서 동작하는 것이 아닌 트랜젝션이 쌓이는 구조인 것이다.

 

그림 3. 백 스텍에 대한 예상 모습과 실제 동작 모습의 차이

 

 

그렇기 때문에 만약 단일 트렌젝션에 여러 fragment를 추가하거나 제거한 경우 백 스택이 pop 될 때 모두 취소되기 때문에 트렌젝션 단위와 addToBackStack을 통해 BackStack에 추가할 트렌젝션을 분리하는 것은 중요하다.

 

예를 들어 fragment를 remove 할 때 해당 트렌젝션을 addToBackStack 하지 않는 경우,

사용자는 뒤로 가기 이벤트를 통해 다시 해당 fragment로 돌아가지 못한다.

 

만약 이때(remove 할 때) addToBackStack 할 경우 해당 fragmant는 STOPPED 상태일 뿐이고, 나중에 사용자가 뒤로 가기 이벤트 등로 뒤로 탐색할 경우 다시 RESUMED 상태로 돌아온다.

자세한 내용은 프래그먼트 수명 주기를 참고할 수 있다.

 

 

 

popBackStack(name)을 통해 특정 트렌젝션으로 pop back 할 수도 있다.

ex) 메인 페이지 -> 상세 페이지 -> 상세 페이지 -> 상세 페이지 -> 상세 페이지

첫 상세 페이지로 이동하는 트렌젝션 name을 first(addToBackStack(first))로 지었을 경우, popBackStack(first)을 할 경우 메인으로 이동 가능하다.

 


뒤로 가기 이벤트에서 하위 프래그먼트를 사용하는 경우

Stack에 더 이상 pop 할 fragmentTransaction이 없고 개발자가 하위 프래그먼트를 사용하지 않는 경우 '뒤로' 이벤트는 Activty까지 전달(버블링) 된다.

 

아까 전에 예외로 두고 설명하지 않은 부분이 있다.

만약, 하위 플래그먼트를 사용할 경우 어떻게 동작할까?

 

우선, Android는 하나의 FragmentManager만 프래그먼트 백 스택을 제어할 수 있다.

 

앱에서 여러 동위 프래그먼트를 동시에 화면에 표시하거나 하위 프래그먼트를 사용하는 경우 FragmentManager 하나를 지정하여 앱의 기본 탐색을 처리해야 한다.

동위 플래그먼트를 동시에 화면에 표시(ex: 그림 1의 Example 1)

 

기본적으로 Back 이벤트가 발생하면 가장 안쪽 레이어가 탐색 동작을 제어한다.

가장 안쪽 레이어에 더 이상 pop Back 할 fragment Transaction이 없으면 제어가 다음 레이어로 돌아가고 이 프로세스는 활동에 도달할 때까지 반복된다.

 

또한, 두 개 이상의 프래그먼트가 동시에 표시되는 경우(그림 1 Example 1), 그중 하나만이 기본 탐색 프래그먼트이다.

프래그먼트를 기본 탐색 프래그먼트로 설정하면 이전 프래그먼트의 지정이 제거된다.

즉, 세부 프래그먼트를 기본 탐색 프래그먼트로 설정하면 기본 프래그먼트의 지정이 제거되는 것이다.

 

 

어떤 FragmentManger를 제어할지 정하거나 변경하기 위해서는

즉, 프래그먼트 트랜잭션 내에서 기본 탐색 프래그먼트를 정의하기 위해서는

 

트랜잭션에서 setPrimaryNavigationFragment() 메서드를 호출하여 변경하고자 하는 childFragmentManager를 기본 컨트롤로 가지고 있는 프래그먼트의 인스턴스전달해야 한다.

 

예시: setPrimaryNavigationFragment(하위 fragment)

 

 


 

 

자, 그렇다면 앱에서 여러 백 스택을 지원해 줘야 할 경우도 있을 것 같다.

대표적으로 바텀 내비게이션(하단 탐색 메뉴)을 사용하는 경우를 들 수 있다.

 

해당 부분에서는 각각의 백 스택을 지원해줘야 할 필요성이 있을 수도 있다. (...)

 

 

Play 스토어 앱 화면

 

하단 탐색 메뉴 기능에 대한 예상 백 스택 구조

 

 

즉, 앱 메뉴에서 실컷 탐색하다 검색 메뉴로 가서 실컷 검색하다 다시 앱 메뉴로 올 경우, 앱 메뉴 백 스택을 복원할지 말지에 대한 동작 정의가 필요하다.

 

Play Store는 굳이 해당 백 스택을 복원하지 않기로 결정했지만 어떤 요구사항에서는 복원하길 원할 수도 있는 것이다.

??? : 앱 메뉴를 실컷 탐색하다 검색 메뉴에 갔다가 다시 앱 메뉴 갈 경우 앱 메뉴의 마지막 페이지(정확하게는 백스 택)를 유지시켜 주세요.

 

해당 기능은 FragmentManager의 saveBackStack()restoreBackStack()를 사용하여 구현할 수 있다.

이러한 메서드는 하나의 백 스택을 저장하고 다른 스택을 복원하여, 여러 백 스택 간에 전환을 지원할 수 있도록 한다.

 


우선 여기까지 정리를 마치며,

 

 

Jetpack Navigation에서는 이런 세부 내용까지 고려하지 않고 제어가 가능하다 보니 해당 내용에 대한 학습이 미비했으나, 공부해 보니 상당히 복잡한 처리를 추상화해놓은 라이브러리라는 것이 실감이 났다. (...)

Jetpack Navigation을 잘 사용하기 위해서는 러닝커브가 높다는 말이 이해가 되는 시간이었다.

 


참고 자료

 

https://developer.android.com/guide/fragments/fragmentmanager?hl=ko

 

프래그먼트 관리자  |  App architecture  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 프래그먼트 관리자 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 참고: Navigation 라이브러리를 사용

developer.android.com

 

 

못다 한 이야기

  • 기존 프래그먼트 찾기, 트랜잭션이 commit 되기 전 특이사항
  • 여러 백 스택 지원 실제 사용 방법
  • 프래그먼트 수동 인스턴스화하여 트랜잭션에 추가