탐색 구성요소는 특정 탐색 요소를 프로그래매틱 방식으로 생성하고 이 요소와 상호작용하는 방법을 제공합니다.
NavHostFragment 생성
아래 예와 같이 NavHostFragment.create()
를 사용하여 특정 그래프 리소스가 있는 NavHostFragment
를 프로그래매틱 방식으로 생성할 수 있습니다.
Kotlin
val finalHost = NavHostFragment.create(R.navigation.example_graph) supportFragmentManager.beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit()
Java
NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph); getSupportFragmentManager().beginTransaction() .replace(R.id.nav_host, finalHost) .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true" .commit();
setPrimaryNavigationFragment(finalHost)
를 사용하면 NavHost
가 시스템 뒤로 버튼 누름을 가로챌 수 있습니다. 또한 app:defaultNavHost="true"
를 추가하여 NavHost
XML에서 이 동작을 구현할 수도 있습니다. 맞춤 뒤로 버튼 동작을 구현하고 NavHost
가 뒤로 버튼 누름을 가로채지 않게 하려면 null
을 setPrimaryNavigationFragment()
에 전달하세요.
NavBackStackEntry를 사용하여 대상 참조
Navigation 2.2.0부터 NavController.getBackStackEntry()
를 호출하여 대상 ID를 전달함으로써 탐색 스택에 있는 대상의 NavBackStackEntry
참조를 가져올 수 있습니다. 지정된 대상의 인스턴스가 백 스택에 2개 이상 포함되어 있다면 getBackStackEntry()
는 스택에서 최상위 인스턴스를 반환합니다.
반환된 NavBackStackEntry
는 대상 수준에서 Lifecycle
, ViewModelStore
및 SavedStateRegistry
를 제공합니다. 이러한 객체는 백 스택에 있는 대상의 전체 기간 동안 유효합니다. 연결된 대상이 백 스택에서 사라지면 Lifecycle
이 소멸되고 상태가 더 이상 저장되지 않으며 ViewModel
객체가 삭제됩니다.
이러한 속성은 사용하는 대상 유형에 상관없이 저장된 상태로 작동하는 ViewModel
객체 및 클래스의 Lifecycle
과 저장소를 제공합니다. 이는 맞춤 대상과 같이 연결된 Lifecycle
이 자동으로 포함되지 않는 대상 유형 작업 시 특히 유용합니다.
예를 들어 프래그먼트 또는 활동의 Lifecycle
을 관찰하는 것처럼 NavBackStackEntry
의 Lifecycle
을 관찰할 수 있습니다. 또한 NavBackStackEntry
는 LifecycleOwner
입니다. 즉, 다음 예와 같이 LiveData
를 관찰할 때 또는 다른 수명 주기 인식 구성요소와 함께 사용할 수 있습니다.
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
navigate()
를 호출할 때마다 수명 주기 상태가 자동으로 업데이트됩니다.
백 스택의 맨 위에 있지 않은 대상의 수명 주기 상태는 대상이 대화상자 대상과 같은 FloatingWindow
대상 아래에 계속 표시되면 RESUMED
에서 STARTED
로 전환되고 그러지 않으면 STOPPED
로 전환됩니다.
이전 대상으로 결과 반환
Navigation 2.3 이상에서 NavBackStackEntry
는 SavedStateHandle
액세스 권한을 부여합니다.
SavedStateHandle
은 데이터를 저장하고 검색하는 데 사용할 수 있는 키-값 맵입니다. 이러한 값은 구성 변경을 포함하여 프로세스 중단 시까지 유지되며 동일한 객체를 통해 계속 사용할 수 있습니다. 제공된 SavedStateHandle
을 사용하여 데이터에 액세스하고 대상 간에 데이터를 전달할 수 있습니다.
이는 데이터가 스택에서 사라진 후 대상에서 데이터를 다시 가져오는 메커니즘으로 특히 유용합니다.
대상 B에서 대상 A로 데이터를 다시 전달하려면 먼저, SavedStateHandle
에서 결과를 수신 대기하도록 대상 A를 설정합니다.
이렇게 하려면 getCurrentBackStackEntry()
API를 사용하여 NavBackStackEntry
를 검색한 후 SavedStateHandle
에서 제공한 LiveData
를 observe
합니다.
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val navController = findNavController(); // We use a String here, but any type that can be put in a Bundle is supported navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe( viewLifecycleOwner) { result -> // Do something with the result. } }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { NavController navController = NavHostFragment.findNavController(this); // We use a String here, but any type that can be put in a Bundle is supported MutableLiveData<String> liveData = navController.getCurrentBackStackEntry() .getSavedStateHandle() .getLiveData("key"); liveData.observe(getViewLifecycleOwner(), new Observer<String>() { @Override public void onChanged(String s) { // Do something with the result. } }); }
대상 B에서 getPreviousBackStackEntry()
API를 사용하여 대상 A의 SavedStateHandle
에서 결과를 set
해야 합니다.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
결과를 한 번만 처리하려면 SavedStateHandle
에서 remove()
를 호출하여 결과를 삭제해야 합니다. 결과를 삭제하지 않으면 LiveData
는 새 Observer
인스턴스에 마지막 결과를 계속 반환합니다.
대화상자 대상 사용 시 고려사항
NavHost
의 전체 뷰를 사용하는 대상(예: <fragment>
대상)으로 navigate
할 때 이전 대상의 수명 주기가 중지되어(STOPPED 상태가 됨) SavedStateHandle
에서 제공한 LiveData
콜백이 방지됩니다.
그러나 대화상자 대상으로 이동할 때 이전 대상도 화면에 표시됩니다. 따라서 이전 대상은 현재 대상이 아님에도 불구하고 시작됩니다(STARTED
상태가 됨). 즉, onViewCreated()
와 같은 수명 주기 메서드 내에서 getCurrentBackStackEntry()
를 호출하면 구성 변경 또는 프로세스 중단 및 재생성 이후에 대화상자 대상의 NavBackStackEntry
가 반환됩니다(대화상자가 다른 대상 위에 복원되기 때문에). 따라서 항상 올바른 NavBackStackEntry
를 사용하려면 대상 ID와 함께 getBackStackEntry()
를 사용해야 합니다.
이는 또한 결과 LiveData
에 설정한 Observer
는 대화상자 대상이 여전히 화면에 표시되어 있는 동안에도 트리거된다는 것을 의미합니다. 대화상자 대상이 닫히고 기본 대상이 현재 대상이 될 때만 결과를 확인하려는 경우 NavBackStackEntry
와 연결된 Lifecycle
을 관찰하고 수명 주기가 RESUMED
상태가 될 때만 결과를 가져오면 됩니다.
Kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navController = findNavController(); // After a configuration change or process death, the currentBackStackEntry // points to the dialog destination, so you must use getBackStackEntry() // with the specific ID of your destination to ensure we always // get the right NavBackStackEntry val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment) // Create our observer and add it to the NavBackStackEntry's lifecycle val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME && navBackStackEntry.savedStateHandle.contains("key")) { val result = navBackStackEntry.savedStateHandle.get<String>("key"); // Do something with the result } } navBackStackEntry.lifecycle.addObserver(observer) // As addObserver() does not automatically remove the observer, we // call removeObserver() manually when the view lifecycle is destroyed viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_DESTROY) { navBackStackEntry.lifecycle.removeObserver(observer) } }) }
Java
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); NavController navController = NavHostFragment.findNavController(this); // After a configuration change or process death, the currentBackStackEntry // points to the dialog destination, so you must use getBackStackEntry() // with the specific ID of your destination to ensure we always // get the right NavBackStackEntry final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment); // Create our observer and add it to the NavBackStackEntry's lifecycle final LifecycleEventObserver observer = new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event.equals(Lifecycle.Event.ON_RESUME) && navBackStackEntry.getSavedStateHandle().contains("key")) { String result = navBackStackEntry.getSavedStateHandle().get("key"); // Do something with the result } } }; navBackStackEntry.getLifecycle().addObserver(observer); // As addObserver() does not automatically remove the observer, we // call removeObserver() manually when the view lifecycle is destroyed getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event.equals(Lifecycle.Event.ON_DESTROY)) { navBackStackEntry.getLifecycle().removeObserver(observer) } } }); }
ViewModel로 대상 간 UI 관련 데이터 공유
탐색 백 스택은 각각의 개별 대상뿐만 아니라 개별 대상이 포함된 각 상위 탐색 그래프에 대해서도 NavBackStackEntry
를 저장합니다. 이를 통해 탐색 그래프로 범위가 지정된 NavBackStackEntry
를 검색할 수 있습니다. 탐색 그래프 범위 지정 NavBackStackEntry
는 탐색 그래프로 범위가 지정된 ViewModel
을 생성하는 방법을 제공하므로 이 ViewModel을 통해 그래프 대상 간에 UI 관련 데이터를 공유할 수 있습니다. 이 방식으로 생성된 ViewModel
객체는 연결된 NavHost
및 ViewModelStore
가 삭제되거나 탐색 그래프가 백 스택에서 사라질 때까지 유지됩니다.
다음 예는 탐색 그래프로 범위가 지정된 ViewModel
을 검색하는 방법을 보여줍니다.
Kotlin
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph)
Java
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);
Navigation 2.2.0 이하를 사용 중인 경우 다음 예와 같이 ViewModel과 함께 저장된 상태를 사용하려면 자체 팩토리를 제공해야 합니다.
Kotlin
val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) { SavedStateViewModelFactory(requireActivity().application, requireParentFragment()) }
Java
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph); ViewModelProvider viewModelProvider = new ViewModelProvider( backStackEntry.getViewModelStore(), new SavedStateViewModelFactory( requireActivity().getApplication(), requireParentFragment())); MyViewModel myViewModel = provider.get(myViewModel.getClass());
ViewModel
에 관한 자세한 내용은 ViewModel 개요를 참고하세요.
확장된 탐색 그래프 수정
확장된 탐색 그래프를 런타임에 동적으로 수정할 수 있습니다.
예를 들어 NavGraph
에 바인딩된 BottomNavigationView
가 있는 경우 NavGraph
의 기본 대상은 앱 시작 시 선택되는 탭을 지정합니다. 그러나 사용자 환경설정이 앱 시작 시 로드되는 선호 탭을 지정하는 경우와 같이 이 동작을 재정의해야 할 수 있습니다. 또는 앱에서 이전 사용자 행동을 기반으로 시작 탭을 변경해야 할 수 있습니다. 이러한 사례는 NavGraph
의 기본 대상을 동적으로 지정하여 지원할 수 있습니다.
다음 NavGraph
를 생각해 보겠습니다.
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph" app:startDestination="@id/home"> <fragment android:id="@+id/home" android:name="com.example.android.navigation.HomeFragment" android:label="fragment_home" tools:layout="@layout/fragment_home" /> <fragment android:id="@+id/location" android:name="com.example.android.navigation.LocationFragment" android:label="fragment_location" tools:layout="@layout/fragment_location" /> <fragment android:id="@+id/shop" android:name="com.example.android.navigation.ShopFragment" android:label="fragment_shop" tools:layout="@layout/fragment_shop" /> <fragment android:id="@+id/settings" android:name="com.example.android.navigation.SettingsFragment" android:label="fragment_settings" tools:layout="@layout/fragment_settings" /> </navigation>
이 그래프가 로드되면 app:startDestination
속성은 HomeFragment
가 표시되도록 지정합니다. 시작 대상을 동적으로 재정의하려면 다음 단계를 따르세요.
- 먼저
NavGraph
를 수동으로 확장합니다. - 시작 대상을 재정의합니다.
- 마지막으로 수동으로 그래프를
NavController
에 연결합니다.
Kotlin
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph) navGraph.startDestination = R.id.shop navController.graph = navGraph binding.bottomNavView.setupWithNavController(navController)
Java
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() .findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph); navGraph.setStartDestination(R.id.shop); navController.setGraph(navGraph); NavigationUI.setupWithNavController(binding.bottomNavView, navController);
이제 앱이 시작되면 HomeFragment
대신 ShopFragment
가 표시됩니다.
딥 링크를 사용하면 NavController
에서 딥 링크 대상의 백 스택을 자동으로 구성합니다. 사용자가 딥 링크로 이동한 후 뒤로 이동하면 어느 시점에는 시작 대상에 도달하게 됩니다. 이전 예에 나와 있는 기법을 사용하여 시작 대상을 재정의하는 경우 구성된 백 스택에 올바른 시작 대상이 추가됩니다.
이 기법을 사용하면 필요에 따라 NavGraph
의 다른 측면도 재정의할 수 있습니다. 그래프의 모든 수정은 setGraph()
를 호출하기 전에 이루어져야 합니다. 그래야 딥 링크를 처리하고 상태를 복원하고 그래프의 시작 대상으로 이동할 때 올바른 구조가 사용됩니다.