본문 바로가기
2022년

프래그먼트 관리자

by 박상윤 2021. 12. 4.

FragmentManager는 앱 프래그먼트에서 작업을 추가, 삭제 또는 교체하고 백 스택에 추가하는 등의 작업을 실행하는 클래스이다.

 

Jetpack Navigation 라이브러리를 사용하는 경우 FragmentManager와 직접 상호작용하지 않을 수 있다. 개발자를 대신해 이 라이브러리가 FragmentManager를 사용하기 때문이다. 그러나 프래그먼트를 사용하는 모든 앱은 일정 수준에서 FragmentManager를 사용하므로 프래그먼트 관리자가 무엇인지 어떻게 작동하는지 파악하는 것이 중요하다.

 

프래그먼트 관리자에 액세스

활동에서 액세스

 

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

 

프래그먼트에서 액세스

 

프래그먼트는 하위 프래그먼트를 하나 이상 호스팅할 수도 있다. 프래그먼트 내에서 getChildFragmentManager()를 통해 프래그먼트의 하위 요소를 관리하는 FragmentManager 참조를 가져올 수 있다. 호스트 FragmentManager에 액세스해야 한다면 getParentFragmentManager()를 사용하게 된다.

 

위 그림에서 보여 주는 두 가지 예에는 각각 단일 활동 호스트가 있다. 두 가지 예의 호스트 활동은 호스트 프래그먼트를 앱에서 다양한 화면으로 교체하는 작업을 담당하는 BottomNavigationView로 최상위 탐색을 사용자에게 표시하며 각 화면은 별도의 프래그먼트로 구현된다.

 

예1의 호스트 프래그먼트는 분할 뷰 화면을 구성하는 하위 프래그먼트 두 개를 호스팅한다. 예2의 호스트 프래그먼트는 스와이프 뷰의 디스플레이 프래그먼트를 구성하는 하위 프래그먼트 한 개를 호스팅한다.

 

이 설정을 고려해 볼 때 하위 프래그먼트를 관리하는 FragmentManager가 각 호스트에 연결되어 있다고 생각할 수 있다. supportFragmentManager, parentFragmentManager, childFragmentManager 사이의 속성 매핑과 함께 그림 2에 나와 있다.

 

그림2. 하위 프래그먼트를 관리하는 자체 FragmentManger가 연결되어 있는 가 호스트

 

참조할 적절한 FragmentManager 속성은 호출 사이트가 프래그먼트 계층 구조에서 어디에 있는지 개발자가 어떤 프래그먼트 관리자에 엑세스하려는지에 따라 다르다.

 

FragmentManager 참조가 있으면 이를 사용하여 사용자에게 표시되는 프래그먼트를 조작할 수 있다.

 

하위 프래그먼트

일반적으로 앱은 애플리케이션 프로젝트에서 단일 또는 소수의 활동으로 구성되어야 하고 각 활동은 관련 화면 그룹을 나타낸다. 활동은 최상위 탐색을 배치하는 지점과 ViewModels 및 프래그먼트 간 다른 뷰 상태의 범위 지정 위치를 제공할 수 있다. 앱의 개별 대상은 프래그먼트로 표시해야 한다.

 

여러 프래그먼트를 한 번에 표시하려면(예: 분할 뷰 또는 대시보드) 대상 프래그먼트와 그 하위 프래그먼트 관리자에서 관리하는 하위 프래그먼트를 사용해야 한다.

 

하위 프래그먼트의 다른 사용 사례는 다음과 같다.

- 일련의 하위 프래그먼트 뷰를 관리하기 위해 상위 프래그먼트에 ViewPager2가 있는 화면 슬라이드

- 일련의 관련 화면 내 하위 탐색

- 하위 프래그먼트를 개별 대상으로 사용하는 Jetpack Navigation. 활동은 단일 상위 NavHostFragment를 호스팅하고 그 공간을 사용자가 앱을 탐색할 때 다른 하위 대상 프래그먼트로 채운다.

 

프래그먼트 관리자 사용

FragmentManager는 프래그먼트 백 스택을 관리한다. 런타임 시 FragmentManager는 사용자 상호작용에 응답하여 프래그먼트를 추가하거나 삭제하는 등 백 스택 작업을 실행할 수 있다. 각 변경사항 집합은 FragmentTransaction 이라는 단일 단위로 함께 커밋된다. 프래

 

사용자가 기기에서 뒤로 버튼을 누르는 경우 또는 개발자가 FragmentManager.popBackStack()을 호출하는 경우 최상위 프래그먼트 트랜잭션이 스택에서 사라진다. 즉, 트랜잭션이 취소된다. 스택에 더 이상 프래그먼트 트랜잭션이 없고 개발자가 하위 프래그먼트를 사용하지 않는 경우 뒤로 이벤트가 활동까지 채워진다.

 

트랜잭션에서 addToBackStack()을 호출하면 여러 프래그먼트 추가, 여러 컨테이너의 프래그먼트 교체 등 많은 작업이 트랜잭션에 포함될 수 있다.백 스택이 표시되면 이러한 모든 작업이 단일 원자 작업으로 취소된다.

popBackStack() 호출 전에 추가 트랜잭션을 커밋한 경우 그리고 트랜잭션에 addToBackStack()을 사용하지 않은 경우 이러한 작업은 취소되지 않는다. 따라서 단일 FragmentTransaction 내에서 백 스택에 영향을 미치는 트랜잭션을 영향을 미치지 않는 트랜잭션과 인터리빙하지 말자.

트랜잭션 실행

레이아웃 컨테이너 내에 프래그먼트를 표시하려면 FragmentManager를 사용하여 FragmentTransaction을 만든다.

그러면 트랜잭션 내 컨테이너에서 add() 또는 replace() 작업을 실행할 수 있다.

 

간단한 FragmentTransaction 예제

이 예에서 ExampleFragment는 현재 R.id.fragment_container ID로 식별된 레이아웃 컨테이너에 있는 프래그먼트를 대체한다. 프래그먼트의 클래스를 replace() 메서드에 제공하면 FragmentManager에서 FragmentFactory를 사용하여 인스턴스화를 처리할 수 있다.

 

setReorderingAllowed(true)는 애니메이션과 전환이 올바르게 작동하도록 트랜잭션과 관련된 프래그먼트의 상태 변경을 최적화한다.

 

addToBackStack()을 호출하면 트랜잭션이 백 스택에 커밋된다. 사용자는 나중에 트랜잭션을 취소하고 뒤로 버튼을 눌러 이전  프래그먼트를 다시 가져올 수 있다. 단일 트랜잭션 내에서 여러 프래그먼트를 추가하거나 삭제한 경우 이러한 모든 작업은 백 스택이 표시되면 실행취소된다. addToBackStack() 호출에 제공된 선택적 이름을 통해 popBackStack()을 사용하여 특정 트랜잭션으로 다시 돌아갈 수 있다.

 

프래그먼트를 삭제하는 트랜잭션을 실행할 때 addToBackStack()을 호출하지 않으면 삭제된 프래그먼트가 트랜잭션이 커밋될 때 소멸되므로 사용자가 이를 다시 탐색할 수 없다. 프래그먼트를 삭제할 때 addToBackStack()을 호출하면 프래그먼트는 STOPPED 상태일 뿐이고 나중에 사용자가 뒤로 탐색할 때 RESUMED 상태가 된다. 이 경우 뷰가 소멸된다.

 

기존 프래그먼트 찾기

findFragmentById()를 사용하여 레이아웃 컨테이너 내의 현재 프래그먼트 참조를 가져올 수 있다.

findFragmentById()를 사용하여 프래그먼트를 XML에서 확장될 때 주어진 ID로 또는 FragmentTransaction에 추가할 때 컨테이너 ID로 찾는다.

또는 고유한 태그를 프래그먼트에 할당하고 findFragmentByTag()를 사용하여 참조를 가져올 수 있다. 레이아웃 내에서 정의되거나 

FragmentTransaction 내에서 add() 또는 replace() 작업 중에 정의된 프래그먼트에서 android:tag XML 속성을 사용하여 태그를 할당할 수 있다.

하위 및 동위 프래그먼트 특별 고려사항

특정 시점에서 프래그먼트 백 스택을 제어하는 데 FragmentManager 하나만 허용된다. 앱에서 여러 동위 프래그먼트를 동시에 화면에 표기하거나 하위 프래그먼트를 사용하는 경우 FragmentManager 하나를 지정하여 앱의 기본 탐색을 처리해야한다.

 

프래그먼트 트랜잭션 내에서 기본 탐색 프래그먼트를 정의하려면 트랜잭션에서 setPrimaryNavigationFragment() 메서드를 호출하여 childFragmentManager에 기본 컨트롤이 있어야 하는 프래그먼트의 인스턴스를 전달한다.

 

탐색 구조를 일련의 레이어로, 활동을 가장 바깥쪽 레이어로 간주하여 하위 프래그먼트의 각 레이어를 아래에 래핑한다. 각 레이어에는 기본 탐색 프래그먼트가 하나씩 있어야 한다. 뒤로 이벤트가 발생하면 가장 안쪽 레이어가 탐색 동작을 제어한다. 가장 안쪽 레이어에 다시 표시할 프래그먼트 트랜잭션이 더 이상 없으면 컨트롤은 다음 바깥 레이어로 돌아가며 이 프로세스는 활동에 도달할 때까지 반복된다.

 

동시에 프래그먼트가 두 개 이상 표시되면 그 중 하나만 기본 탐색 프래그먼트가 될  수 있다. 프래그먼트를 기본탐색 프래그먼트로 설정하면 이전 프래그먼트의 지정이 삭제된다. 이전 예를 사용하여 세부 프래그먼트를 기본 탐색 프래그먼트로 설정하면 기본 프래그먼트의 지정이 삭제된다.

 

여러 백 스택 지원

앱에서 여러 개의 백 스택을 지원해야 하는 경우가 있다. 일반적인 예로 앱에서 하단 탐색 메뉴를 사용하는 경우를 들 수 있다. FragmentManager를 사용하면 saveBackStack() 및 restoreBackStack()메서드로 여러 백 스택을 지원할 수  있다. 이러한 메서드는 하나의 백 스택을 저장하고 다른 스택을 복원하여 여러 백스택 간에 전환할 수 있도록 지원한다.

 

참고, 또 다른 방법으로, 하단 탐색에 관한 여러 백 스택 지원을 자동으로 처리하는 NavigationUI 구성요소를 사용할 수 도 있다.

 

saveBackStack()은 선택적 name 매개변수를 사용하여 popBackStack()을 호출하는 것과 비슷하게 작동한다. 즉, 지정된 트랜잭션과 스택에서 이 트랜잭션 이후에 있는 모든 트랜잭션이 표시된다. 차이점은 saveBackStack()은 표시된 트랜잭션에 있는 모든 프래그먼트의 상태를 저장한다는 것이다.

 

예를 들어, 이전에 addToBackStack()을 사용하여 FragmentTransaction을 커밋해서 백 스택에 프래그먼트를 추가했다고 가정해보자.

이 경우 saveState()를 호출하여 이 프래그먼트 트랜잭션과 ExampleFragment의 상태를 저장할 수 있다.

참고 : 트랜잭션을 단일 원자성 작업으로 복원하려면 setReorderingAllowed(true)를 호출하는 트랜잭션에서만 saveBackStack()을 사용해야 한다.

 

동일한 이름 매개변수를 사용하여 restoreBackStack()을 호출하면 모든 표시된 트랜잭션과 모든 저장된 프래그먼트 상태를 복원할 수 있다.

참고 : addToBackStack()과 함께 프래그먼트 트랜잭션의 선택적 이름을 전달하지 않았다면 saveBackStack() 및 restoreBackStack()을 사용할 수 없다.

 

프래그먼트에 종속 항목 제공

프래그먼트를 추가할 때 프래그먼트를 수동으로 인스턴스화하여 FragmentTransaction에 추가할 수 있다,

 

프래그먼트 트랜잭션을 커밋할 때 개발자가 만든 프래그먼트의 인스턴스가 사용된 인스턴스입니다.

그러나 구성 변경 중에 활동의 모든 프래그먼트가 소멸되가 가장 적합한 Android 리소스로 다시 만들어 집니다.

FragmentManager가 이 모든 작업을 처리한다. 프래그먼트의 인스턴스를 다시 만들고 호스트에 연결하여 백 스택 상태를 다시 만든다.

 

기본적으로 FragmentManager는 프레임워크에서 제공하는 FragmentFactory를 사용하여 프래그먼트의 새 인스턴스를 인스턴스화합니다. 이 기본 팩토리는 리플렉션을 사용하여 프래그먼트의 인수가 없는 생성자를 찾아 호출한다. 즉, 이 기본 팩토리를 사용하여 프래그먼트에 종속 항목을 제공할 수 없다. 또한 처음 프래그먼트를 만드는 데 사용한 모든 맞춤 생성자가 재 생성 중에 기본적으로 사용되지 않습니다.

 

프래그먼트에 종속 항목을 제공하거나 맞춤 생성자를 사용하려면 대신 맞춤 FragmentFactory 서브 클래스를 만들고 FragmentFactory.instantiate를 재정의해야 한다. 그러면 맞춤 팩토리로 FragmentManager의 기본 팩토리를 재정의 할 수 있다. 맞춤 팩토리는 프래그먼트를 인스턴스화하는데 사용된다.

 

거주 지역의 인기 디저트를 표기하는 DessertFragment가 있다고 가정해 보겠습니다. 사용자에게 올바른 UI를 표시하는 데 필요한 정보를 제공하는 DessertsRepository 클래스의 종속 항목이 DessertsFragment에 있다고 가정해 보자.

 

생성자에서 DessertsRepository 인스턴스를 요구하도록 DessertsFragment를 정의할 수 있다.

 

FragmentFactory의 간단한 구현은 다음과 유사하다.

이 예에서는 FragmentFactory를 서브클래스로 분류하여 DessertsFragment의 맞춤 프래그먼트 생성 로직을 제공하는 instantiate() 메서드를 재정의한다. 다른 프래그먼트 클래스는 super.instantiate()를 통해 FragmentFactory의 기본 동작으로 처리된다.

 

그러면 FragmentManager에서 속성을 설정하여 앱의 프래그먼트를 구성할 때 사용할 팩토리로 MyFragmentFactory를 지정할 수 있다. 프래그먼트를 다시 만들 때 MyFragmentFactory가 사용되도록 하려면 활동의 super.onCreate()이전에 이 속성을 설정해야 한다.

 

활동에서 FragmentFactroy를 설정하면 활동의 프래그먼트 계층 구조 전체에 걸쳐 프래그먼트 생성이 재정의된다.

즉, 추가하는 모든 하위 프래그먼트의 childFragmentManager가 하위 수준에서 재정의되지 않는 한 여기에서 설정된 맞춤 프래그먼트 팩토리를 사용한다.

 

FragmentFactory로 테스트

단일 활동 아키텍처에서는 FragmentScenario 클래스를 사용하여 프래그먼트를 격리된 상태로 테스트해야 한다. 활동의 맞춤 onCreate 로직에 의존할 수 없으므로 다음 예와 같이 대신 FragmentFactory를 프래그먼트 테스트의 인수로 전달할 수 있다.

 

'2022년' 카테고리의 다른 글

1.1 데이터 타입의 종류  (0) 2021.12.04
프래그먼트 트랜잭션  (0) 2021.12.04
Dialog  (0) 2021.10.22
SnackBar  (0) 2021.10.22
Toast  (0) 2021.10.21