FragmentManager
는 앱 프래그먼트에서 프래그먼트를 추가, 삭제 또는 교체하고 백 스택에 추가하는 등의 작업을 실행하는 클래스입니다.
Jetpack Navigation 라이브러리를 사용하는 경우 FragmentManager
와의 직접적인 상호작용은 거의 필요하지 않습니다. 개발자를 대신해 이 라이브러리가 FragmentManager
를 사용하기 때문입니다. 그러나 프래그먼트를 사용하는 모든 앱은 어느 정도 FragmentManager
를 사용하므로 프래그먼트 관리자가 무엇인지 어떻게 작동하는지 파악하는 것이 중요합니다.
이 페이지에서 다루는 내용은 다음과 같습니다.
FragmentManager
에 액세스하는 방법- 활동 및 프래그먼트와 관련한
FragmentManager
의 역할 FragmentManager
로 백 스택을 관리하는 방법- 프래그먼트에 데이터와 종속 항목을 제공하는 방법
FragmentManager에 액세스
FragmentManager
에는 활동 또는 프래그먼트에서 액세스할 수 있습니다.
FragmentActivity
및 그 서브클래스(예: AppCompatActivity
)는 getSupportFragmentManager()
메서드를 통해 FragmentManager
에 액세스할 수 있습니다.
프래그먼트는 하나 이상의 하위 프래그먼트를 호스팅할 수 있습니다. 프래그먼트 내에서 getChildFragmentManager()
를 통해 프래그먼트의 하위 요소를 관리하는 FragmentManager
참조를 가져올 수 있습니다.
호스트 FragmentManager
에 액세스해야 한다면 getParentFragmentManager()
를 사용하면 됩니다.
몇 가지 예를 통해 프래그먼트, 프래그먼트의 호스트, 각각과 연결된 FragmentManager
인스턴스 간의 관계를 확인해 보겠습니다.
그림 1에서 보여 주는 두 가지 예에는 각각 단일 활동 호스트가 있습니다. 두 가지 예의 호스트 활동은 호스트 프래그먼트를 앱에서 다양한 화면으로 교체하는 작업을 담당하는 BottomNavigationView
로 최상위 탐색을 사용자에게 표시하며 각 화면은 별도의 프래그먼트로 구현됩니다.
예 1의 호스트 프래그먼트는 분할 뷰 화면을 구성하는 하위 프래그먼트 두 개를 호스팅합니다. 예 2의 호스트 프래그먼트는 스와이프 뷰의 디스플레이 프래그먼트를 구성하는 하위 프래그먼트 한 개를 호스팅합니다.
이 설정을 고려해 볼 때 하위 프래그먼트를 관리하는 FragmentManager
가 각 호스트에 연결되어 있다고 생각할 수 있습니다. supportFragmentManager
, parentFragmentManager
, childFragmentManager
사이의 속성 매핑과 함께 그림 2에 나와 있습니다.
참조할 적절한 FragmentManager
속성은 호출 사이트가 프래그먼트 계층 구조에서 어디에 있는지 개발자가 어떤 프래그먼트 관리자에 액세스하려는지에 따라 다릅니다.
FragmentManager
참조가 있으면 이를 사용하여 사용자에게 표시되는 프래그먼트를 조작할 수 있습니다.
하위 프래그먼트
일반적으로 앱은 애플리케이션 프로젝트에서 단일 또는 소수의 활동으로 구성되고 각 활동은 관련 화면 그룹을 나타냅니다. 활동은 최상위 탐색을 배치하는 지점과 ViewModel
객체 및 프래그먼트 간 다른 뷰 상태의 범위 지정 위치를 제공할 수 있습니다. 프래그먼트는 앱의 개별 대상을 나타냅니다.
여러 프래그먼트를 한 번에 표시하려면(예: 분할 뷰 또는 대시보드) 대상 프래그먼트와 그 하위 프래그먼트 관리자에서 관리하는 하위 프래그먼트를 사용할 수 있습니다.
하위 프래그먼트의 다른 사용 사례는 다음과 같습니다.
- 일련의 하위 프래그먼트 뷰를 관리하기 위해 상위 프래그먼트에
ViewPager2
가 있는 화면 슬라이드 - 일련의 관련 화면 내 하위 탐색
- 하위 프래그먼트를 개별 대상으로 사용하는 Jetpack Navigation. 활동은 단일 상위
NavHostFragment
를 호스팅하고 그 공간을 사용자가 앱을 탐색할 때 다른 하위 대상 프래그먼트로 채웁니다.
FragmentManager 사용
FragmentManager
는 프래그먼트 백 스택을 관리합니다. 런타임 시 FragmentManager
는 사용자 상호작용에 응답하여 프래그먼트를 추가하거나 삭제하는 등 백 스택 작업을 실행할 수 있습니다. 각 변경사항 집합은 FragmentTransaction
이라는 단일 단위로 함께 커밋됩니다.
프래그먼트 트랜잭션에 관한 자세한 내용은 프래그먼트 트랜잭션 가이드를 참고하세요.
사용자가 기기에서 뒤로 버튼을 누르는 경우 또는 개발자가 FragmentManager.popBackStack()
을 호출하는 경우 최상위 프래그먼트 트랜잭션이 스택에서 사라집니다. 스택에 더 이상 프래그먼트 트랜잭션이 없고 개발자가 하위 프래그먼트를 사용하지 않는 경우 뒤로 이벤트가 활동까지 채워집니다. 하위 프래그먼트를 사용하는 경우 하위 및 동위 프래그먼트 특별 고려사항을 참고하세요.
트랜잭션에서 addToBackStack()
을 호출하면 여러 프래그먼트 추가, 여러 컨테이너의 프래그먼트 교체 등 많은 작업이 트랜잭션에 포함될 수 있습니다.
백 스택이 표시되면 이러한 모든 작업이 단일 원자 작업으로 취소됩니다. 그러나 popBackStack()
호출 전에 추가 트랜잭션을 커밋한 경우 그리고 트랜잭션에 addToBackStack()
을 사용하지 않은 경우 이러한 작업은 취소되지 않습니다. 따라서 단일 FragmentTransaction
내에서 백 스택에 영향을 미치는 트랜잭션을 영향을 미치지 않는 트랜잭션과 인터리빙하지 마세요.
트랜잭션 실행
레이아웃 컨테이너 내에 프래그먼트를 표시하려면 FragmentManager
를 사용하여 FragmentTransaction
을 만듭니다. 그러면 트랜잭션 내 컨테이너에서 add()
또는 replace()
작업을 실행할 수 있습니다.
예를 들어 간단한 FragmentTransaction
은 다음과 같습니다.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack("name") // Name can be null .commit();
이 예에서 ExampleFragment
는 현재 R.id.fragment_container
ID로 식별된 레이아웃 컨테이너에 있는 프래그먼트(있는 경우)를 대체합니다. 프래그먼트의 클래스를 replace()
메서드에 제공하면 FragmentManager
에서 FragmentFactory
를 사용하여 인스턴스화를 처리할 수 있습니다.
자세한 내용은 프래그먼트에 종속 항목 제공 섹션을 참고하세요.
setReorderingAllowed(true)
는 애니메이션과 전환이 올바르게 작동하도록 트랜잭션과 관련된 프래그먼트의 상태 변경을 최적화합니다. 애니메이션과 전환으로 탐색하는 방법에 관한 자세한 내용은 프래그먼트 트랜잭션 및 애니메이션을 사용하여 프래그먼트 간 탐색을 참고하세요.
addToBackStack()
을 호출하면 트랜잭션이 백 스택에 커밋됩니다. 사용자는 나중에 트랜잭션을 취소하고 뒤로 버튼을 눌러 이전 프래그먼트를 다시 가져올 수 있습니다. 단일 트랜잭션 내에서 여러 프래그먼트를 추가하거나 삭제한 경우 이러한 모든 작업은 백 스택이 표시되면 실행취소됩니다. addToBackStack()
호출에 제공된 선택적 이름을 통해 popBackStack()
을 사용하여 특정 트랜잭션으로 다시 돌아갈 수 있습니다.
프래그먼트를 삭제하는 트랜잭션을 실행할 때 addToBackStack()
을 호출하지 않으면 삭제된 프래그먼트가 트랜잭션이 커밋될 때 소멸되므로 사용자가 이를 다시 탐색할 수 없습니다. 프래그먼트를 삭제할 때 addToBackStack()
을 호출하면 프래그먼트는 STOPPED
상태일 뿐이고 나중에 사용자가 뒤로 탐색할 때 RESUMED
상태가 됩니다. 이 경우 뷰가 소멸됩니다. 자세한 내용은 프래그먼트 수명 주기를 참고하세요.
기존 프래그먼트 찾기
findFragmentById()
를 사용하여 레이아웃 컨테이너 내의 현재 프래그먼트 참조를 가져올 수 있습니다.
findFragmentById()
를 사용하여 프래그먼트를 XML에서 확장할 때 주어진 ID로 또는 FragmentTransaction
에 추가할 때 컨테이너 ID로 찾습니다. 예를 들면 다음과 같습니다.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
또는 고유한 태그를 프래그먼트에 할당하고 findFragmentByTag()
를 사용하여 참조를 가져올 수 있습니다.
레이아웃 내에서 정의되거나 FragmentTransaction
내에서 add()
또는 replace()
작업 중에 정의된 프래그먼트에서 android:tag
XML 속성을 사용하여 태그를 할당할 수 있습니다.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container, "tag") setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null, "tag") .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
하위 및 동위 프래그먼트 특별 고려사항
하나의 FragmentManager
만 프래그먼트 백 스택을 제어할 수 있습니다. 앱에서 여러 동위 프래그먼트를 동시에 화면에 표시하거나 하위 프래그먼트를 사용하는 경우 FragmentManager
하나를 지정하여 앱의 기본 탐색을 처리해야 합니다.
프래그먼트 트랜잭션 내에서 기본 탐색 프래그먼트를 정의하려면 트랜잭션에서 setPrimaryNavigationFragment()
메서드를 호출하여 childFragmentManager
에 기본 컨트롤이 있는 프래그먼트의 인스턴스를 전달합니다.
탐색 구조를 일련의 레이어로, 활동을 가장 바깥쪽 레이어로 간주하여 하위 프래그먼트의 각 레이어를 아래에 래핑합니다. 각 레이어에는 기본 탐색 프래그먼트가 하나씩 있습니다.
뒤로 이벤트가 발생하면 가장 안쪽 레이어가 탐색 동작을 제어합니다. 가장 안쪽 레이어에 다시 표시할 프래그먼트 트랜잭션이 더 이상 없으면 컨트롤은 다음 바깥 레이어로 돌아가며 이 프로세스는 활동에 도달할 때까지 반복됩니다.
동시에 프래그먼트가 두 개 이상 표시되면 그중 하나만 기본 탐색 프래그먼트가 됩니다. 프래그먼트를 기본 탐색 프래그먼트로 설정하면 이전 프래그먼트의 지정이 삭제됩니다. 이전 예를 사용하여 세부 프래그먼트를 기본 탐색 프래그먼트로 설정하면 기본 프래그먼트의 지정이 삭제됩니다.
여러 백 스택 지원
앱에서 여러 개의 백 스택을 지원해야 하는 경우가 있습니다. 일반적인 예로 앱에서 하단 탐색 메뉴를 사용하는 경우를 들 수 있습니다. FragmentManager
를 사용하면 saveBackStack()
및 restoreBackStack()
메서드로 여러 백 스택을 지원할 수 있습니다. 이러한 메서드는 하나의 백 스택을 저장하고 다른 스택을 복원하여 여러 백 스택 간에 전환할 수 있도록 지원합니다.
saveBackStack()
은 선택적 name
매개변수를 사용하여 popBackStack()
을 호출하는 것과 비슷하게 작동합니다. 즉, 지정된 트랜잭션과 스택에서 이 트랜잭션 이후에 있는 모든 트랜잭션이 표시됩니다. 차이점은 saveBackStack()
은 표시된 트랜잭션에 있는 모든 프래그먼트의 상태를 저장한다는 것입니다.
예를 들어, 아래의 예와 같이 이전에 addToBackStack()
을 사용하여 FragmentTransaction
을 커밋해서 백 스택에 프래그먼트를 추가했다고 가정해 보겠습니다.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) // setReorderingAllowed(true) and the optional string argument for // addToBackStack() are both required if you want to use saveBackStack() .setReorderingAllowed(true) .addToBackStack("replacement") .commit();
이 경우 saveBackStack()
를 호출하여 이 프래그먼트 트랜잭션과 ExampleFragment
의 상태를 저장할 수 있습니다.
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
동일한 이름 매개변수를 사용하여 restoreBackStack()
을 호출하면 모든 표시된 트랜잭션과 모든 저장된 프래그먼트 상태를 복원할 수 있습니다.
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
프래그먼트에 종속 항목 제공
프래그먼트를 추가할 때 프래그먼트를 수동으로 인스턴스화하여 FragmentTransaction
에 추가할 수 있습니다.
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
프래그먼트 트랜잭션을 커밋할 때 개발자가 만든 프래그먼트의 인스턴스가 사용된 인스턴스입니다. 그러나 구성 변경 중에 활동과 활동의 모든 프래그먼트가 소멸되고 가장 적합한 Android 리소스로 다시 만들어집니다.
FragmentManager
가 이 모든 작업을 처리합니다. 프래그먼트의 인스턴스를 다시 만들고 호스트에 연결하여 백 스택 상태를 다시 만듭니다.
기본적으로 FragmentManager
는 프레임워크에서 제공하는 FragmentFactory
를 사용하여 프래그먼트의 새 인스턴스를 인스턴스화합니다. 이 기본 팩토리는 리플렉션을 사용하여 프래그먼트의 인수가 없는 생성자를 찾아 호출합니다. 즉, 이 기본 팩토리를 사용하여 프래그먼트에 종속 항목을 제공할 수 없습니다. 또한 처음 프래그먼트를 만드는 데 사용한 모든 맞춤 생성자가 재생성 중에 기본적으로 사용되지 않습니다.
프래그먼트에 종속 항목을 제공하거나 맞춤 생성자를 사용하려면 대신 맞춤 FragmentFactory
서브클래스를 만들고 FragmentFactory.instantiate
를 재정의해야 합니다.
그러면 맞춤 팩토리로 FragmentManager
의 기본 팩토리를 재정의할 수 있습니다. 맞춤 팩토리는 프래그먼트를 인스턴스화하는 데 사용됩니다.
거주 지역의 인기 디저트를 표시하는 DessertsFragment
가 있다고 가정해 보겠습니다. 사용자에게 올바른 UI를 표시하는 데 필요한 정보를 제공하는 DessertsRepository
클래스의 종속 항목이 DessertsFragment
에 있다고 가정해 보세요.
생성자에서 DessertsRepository
인스턴스를 요구하도록 DessertsFragment
를 정의할 수 있습니다.
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
FragmentFactory
의 간단한 구현은 다음과 유사합니다.
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
이 예에서는 FragmentFactory
를 서브클래스로 분류하여 DessertsFragment
의 맞춤 프래그먼트 생성 로직을 제공하는 instantiate()
메서드를 재정의합니다.
다른 프래그먼트 클래스는 super.instantiate()
를 통해 FragmentFactory
의 기본 동작으로 처리됩니다.
그러면 FragmentManager
에서 속성을 설정하여 앱의 프래그먼트를 구성할 때 사용할 팩토리로 MyFragmentFactory
를 지정할 수 있습니다. 프래그먼트를 다시 만들 때 MyFragmentFactory
가 사용되도록 하려면 활동의 super.onCreate()
이전에 이 속성을 설정해야 합니다.
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
활동에서 FragmentFactory
를 설정하면 활동의 프래그먼트 계층 구조 전체에 걸쳐 프래그먼트 생성이 재정의됩니다. 즉, 추가하는 모든 하위 프래그먼트의 childFragmentManager
가 하위 수준에서 재정의되지 않는 한 여기에서 설정된 맞춤 프래그먼트 팩토리를 사용합니다.
FragmentFactory로 테스트
단일 활동 아키텍처에서는 FragmentScenario
클래스를 사용하여 프래그먼트를 격리된 상태로 테스트해야 합니다. 활동의 맞춤 onCreate
로직에 의존할 수 없으므로 다음 예와 같이 대신 FragmentFactory
를 프래그먼트 테스트의 인수로 전달할 수 있습니다.
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
이 테스트 프로세스에 관한 자세한 내용과 전체 예는 프래그먼트 테스트를 참고하세요.