탐색 구성요소와 프로그래매틱 방식으로 상호작용

탐색 구성요소는 특정 탐색 요소를 프로그래매틱 방식으로 생성하고 이 요소와 상호작용하는 방법을 제공합니다.

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가 뒤로 버튼 누름을 가로채지 않게 하려면 nullsetPrimaryNavigationFragment()에 전달하세요.

Navigation 2.2.0부터 NavController.getBackStackEntry()를 호출하여 대상 ID를 전달함으로써 탐색 스택에 있는 대상의 NavBackStackEntry 참조를 가져올 수 있습니다. 지정된 대상의 인스턴스가 백 스택에 2개 이상 포함되어 있다면 getBackStackEntry()는 스택에서 최상위 인스턴스를 반환합니다.

반환된 NavBackStackEntry는 대상 수준에서 Lifecycle, ViewModelStoreSavedStateRegistry를 제공합니다. 이러한 객체는 백 스택에 있는 대상의 전체 기간 동안 유효합니다. 연결된 대상이 백 스택에서 사라지면 Lifecycle이 소멸되고 상태가 더 이상 저장되지 않으며 ViewModel 객체가 삭제됩니다.

이러한 속성은 사용하는 대상 유형에 상관없이 저장된 상태로 작동하는 ViewModel 객체 및 클래스의 Lifecycle과 저장소를 제공합니다. 이는 맞춤 대상과 같이 연결된 Lifecycle이 자동으로 포함되지 않는 대상 유형 작업 시 특히 유용합니다.

예를 들어 프래그먼트 또는 활동의 Lifecycle을 관찰하는 것처럼 NavBackStackEntryLifecycle을 관찰할 수 있습니다. 또한 NavBackStackEntryLifecycleOwner입니다. 즉, 다음 예와 같이 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 이상에서 NavBackStackEntrySavedStateHandle 액세스 권한을 부여합니다. SavedStateHandle은 데이터를 저장하고 검색하는 데 사용할 수 있는 키-값 맵입니다. 이러한 값은 구성 변경을 포함하여 프로세스 중단 시까지 유지되며 동일한 객체를 통해 계속 사용할 수 있습니다. 제공된 SavedStateHandle을 사용하여 데이터에 액세스하고 대상 간에 데이터를 전달할 수 있습니다. 이는 데이터가 스택에서 사라진 후 대상에서 데이터를 다시 가져오는 메커니즘으로 특히 유용합니다.

대상 B에서 대상 A로 데이터를 다시 전달하려면 먼저, SavedStateHandle에서 결과를 수신 대기하도록 대상 A를 설정합니다. 이렇게 하려면 getCurrentBackStackEntry() API를 사용하여 NavBackStackEntry를 검색한 후 SavedStateHandle에서 제공한 LiveDataobserve합니다.

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)
            }
        }
    });
}

탐색 백 스택은 각각의 개별 대상뿐만 아니라 개별 대상이 포함된 각 상위 탐색 그래프에 대해서도 NavBackStackEntry를 저장합니다. 이를 통해 탐색 그래프로 범위가 지정된 NavBackStackEntry를 검색할 수 있습니다. 탐색 그래프 범위 지정 NavBackStackEntry는 탐색 그래프로 범위가 지정된 ViewModel을 생성하는 방법을 제공하므로 이 ViewModel을 통해 그래프 대상 간에 UI 관련 데이터를 공유할 수 있습니다. 이 방식으로 생성된 ViewModel 객체는 연결된 NavHostViewModelStore가 삭제되거나 탐색 그래프가 백 스택에서 사라질 때까지 유지됩니다.

다음 예는 탐색 그래프로 범위가 지정된 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가 표시되도록 지정합니다. 시작 대상을 동적으로 재정의하려면 다음 단계를 따르세요.

  1. 먼저 NavGraph를 수동으로 확장합니다.
  2. 시작 대상을 재정의합니다.
  3. 마지막으로 수동으로 그래프를 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()를 호출하기 전에 이루어져야 합니다. 그래야 딥 링크를 처리하고 상태를 복원하고 그래프의 시작 대상으로 이동할 때 올바른 구조가 사용됩니다.