Navigation (android developers)
https://developer.android.com/guide/navigation/navigation-getting-started#java
- Overview
- Get started with the Navigation component
- Design navigation graphs
- Nested navigation graphs
- Global actions
- Navigate to a destination
- Conditional navigation
1. Overview
# Navigation component 다음 세가지로 구성됨
- Navigation graph: 내비게이션 관련 정보들이 들어있는 XML 리소스
- NavHost: 빈 컨테이너 (destination 을 표시), NavHostFragment
- NavController: destination 교체 담당
2. Get started with the Navigation component
Create a navigation graph
1. res 디렉토리에서 New > Android Resource File
2. 파일이름 : nav_graph
3. 리소스 타입 : Navigation
# app 수준 build.gradle 파일에 navigation dependencies 를 추가하지 않았다면 묻는 창이 뜬다.
Add a NavHost to an activity
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
# app:defaultNavHost=”true” : 시스템의 백 버튼을 인터셉트한다.
# 액티비티의 디자인 창에서 NavHostFragment 를 검색해서 추가해도 된다.
Add destinations to the navigation graph
In the Navigation Editor, click the New Destination icon , and then click Create new destination.
Designate a screen as the start destination
- destination 을 선택하고 메뉴에서 홈버튼을 누룬다.
- 또는 destination 을 선택하고 마우스 오른쪽 버튼 -> Start Destination 선택
Connect destinations
- Navigation Editor 을 연다.
- 출발 destination 의 오른쪽 작은 원 버튼을 누루고 도착 destination 으로 드래그 앤 드롭한다.
- action 이 만들어진다.
Navigate to a destination
# NavController 를 사용해서 navigation 합니다.
# NavHost 는 자신의 NavController 를 가집니다.
# NavController 얻기
방법1.
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController();
방법2.
NavHostFragment.findNavController(Fragment) Navigation.findNavController(Activity, @IdRes int viewId) // viewId 는 NavHostFragment 의 ID 인 R.id.nav_host_fragment Navigation.findNavController(View) // NavHostFragment 의 view
# Navigation.findNavController 는 OnCreate() 에서는 실패한다. 그 이후에서 할 것.
Ensure type-safety by using Safe Args
# To add Safe Args to your project, include the following classpath in your top level build.gradle file:
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.1"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
# module’s build.gradle file:
plugins {
id 'androidx.navigation.safeargs'
}
# You must have android.useAndroidX=true in your gradle.properties file as per Migrating to AndroidX.
Safe Args 를 추가했다면 각 action 에 대한 클래스가 생성된다. (클래스 이름은 시작 destination 의 클래스 이름과 “Directions”)
# SpecifyAmountFragment -> SpecifyAmountFragmentDirections
SpecifyAmountFragmentDirections 클래스는 NavDirections 객체를 반환하는 actionSpecifyAmountFragmentToConfirmationFragment() 단일 메서드를 가진다.
@Override
public void onClick(View view) {
NavDirections action =
SpecifyAmountFragmentDirections
.actionSpecifyAmountFragmentToConfirmationFragment();
Navigation.findNavController(view).navigate(action);
}
3. Design navigation graphs
Nested graphs
# top-level 그래프에서 Nested graph 안에 있는 destination 에 직접 접근할 수 없고 nested graph 에 먼저 접근해야 한다.
Navigation across library modules
<!-- App Module Navigation Graph -->
<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"
app:startDestination="@id/match">
<fragment android:id="@+id/match"
android:name="com.example.android.navigationsample.Match"
android:label="fragment_match">
<!-- Launch into In Game Modules Navigation Graph -->
<action android:id="@+id/action_match_to_in_game_nav_graph"
app:destination="@id/in_game_nav_graph" />
</fragment>
<include app:graph="@navigation/in_game_navigation" />
</navigation>
<!-- Game Module Navigation Graph -->
<?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"
android:id="@+id/in_game_nav_graph"
app:startDestination="@id/in_game">
<fragment
android:id="@+id/in_game"
android:name="com.example.android.gamemodule.InGame"
android:label="Game">
<action
android:id="@+id/action_in_game_to_resultsWinner"
app:destination="@id/results_winner" />
<action
android:id="@+id/action_in_game_to_gameOver"
app:destination="@id/game_over" />
</fragment>
<fragment
android:id="@+id/results_winner"
android:name="com.example.android.gamemodule.ResultsWinner" >
<!-- Action back to destination which launched into this in_game_nav_graph-->
<action android:id="@+id/action_pop_out_of_game"
app:popUpTo="@id/in_game_nav_graph"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/game_over"
android:name="com.example.android.gamemodule.GameOver"
android:label="fragment_game_over"
tools:layout="@layout/fragment_game_over" >
<!-- Action back to destination which launched into this in_game_nav_graph-->
<action android:id="@+id/action_pop_out_of_game"
app:popUpTo="@id/in_game_nav_graph"
app:popUpToInclusive="true" />
</fragment>
</navigation>
4. Nested navigation graphs
Nested navigation graphs
# Shift 키를 눌러서 destination 을 선택하고 마우스 오른쪽 버튼 -> Move to Nested Graph > New Graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.cashdog.cashdog.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" >
<action
android:id="@+id/action_mainFragment_to_sendMoneyGraph"
app:destination="@id/sendMoneyGraph" />
<action
android:id="@+id/action_mainFragment_to_viewBalanceFragment"
app:destination="@id/viewBalanceFragment" />
</fragment>
<fragment
android:id="@+id/viewBalanceFragment"
android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
android:label="fragment_view_balance"
tools:layout="@layout/fragment_view_balance" />
<navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
<fragment
android:id="@+id/chooseRecipient"
android:name="com.example.cashdog.cashdog.ChooseRecipient"
android:label="fragment_choose_recipient"
tools:layout="@layout/fragment_choose_recipient">
<action
android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
app:destination="@id/chooseAmountFragment" />
</fragment>
<fragment
android:id="@+id/chooseAmountFragment"
android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
android:label="fragment_choose_amount"
tools:layout="@layout/fragment_choose_amount" />
</navigation>
</navigation>
Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);
Reference other navigation graphs with <include>
<!-- (root) nav_graph.xml -->
<?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/fragment">
<include app:graph="@navigation/included_graph" />
<fragment
android:id="@+id/fragment"
android:name="com.example.myapplication.BlankFragment"
android:label="Fragment in Root Graph"
tools:layout="@layout/fragment_blank">
<action
android:id="@+id/action_fragment_to_second_graph"
app:destination="@id/second_graph" />
</fragment>
...
</navigation>
<!-- included_graph.xml --> <?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/second_graph" app:startDestination="@id/includedStart"> <fragment android:id="@+id/includedStart" android:name="com.example.myapplication.IncludedStart" android:label="fragment_included_start" tools:layout="@layout/fragment_included_start" /> </navigation>
5. Global actions
Create a global action
- destination 에서 마우스 오른쪽 버튼을 누룬다.
- Add Action > Global
- 작은 화살표가 왼쪽에 생긴다.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_nav"
app:startDestination="@id/mainFragment">
...
<action android:id="@+id/action_global_mainFragment"
app:destination="@id/mainFragment"/>
</navigation>
Use a global action
viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.action_global_mainFragment);
}
});
6. Navigate to a destination
NavController 를 사용해서 destination 으로 갈 수 있다.
각 NavHost (NavHostFragment) 는 NavController 을 가지고 있다.
Kotlin:
Fragment.findNavController()
View.findNavController()
Activity.findNavController(viewId: Int)
Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
Use Safe Args to navigate with type-safety
# To add Safe Args to your project, include the following classpath in your top level build.gradle file:
buildscript { repositories { google() } dependencies { def nav_version = "2.3.1" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } }
# To generate Java language code suitable for Java or mixed Java and Kotlin modules, add this line to your app or module’s build.gradle file:
apply plugin: "androidx.navigation.safeargs"
# to generate Kotlin code suitable for Kotlin-only modules add:
apply plugin: "androidx.navigation.safeargs.kotlin"
# You must have android.useAndroidX=true in your gradle.properties file
Safe Args generates a class for each destination where an action originates.
The generated class name adds “Directions” to the originating destination class name.
For example, if the originating destination is named SpecifyAmountFragment,
the generated class is named SpecifyAmountFragmentDirections.
The generated class contains a static method for each action defined in the originating destination.
This method takes any defined action parameters as arguments and returns a NavDirections object that you can pass directly to navigate().
Safe Args example
SpecifyAmountFragment -> ConfirmationFragment destination 으로 이동한다고 가정하자.
ConfirmationFragment 는 하나의 float 파라미터를 받는다.
Safe Args 는 SpecifyAmountFragmentDirections 클래스를 생성한다.
하나의 메서드 actionSpecifyAmountFragmentToConfirmationFragment()
그리고 inner 클래스 ActionSpecifyAmountFragmentToConfirmationFragment 를 가진다.
inner 클래스는 NavDirections 를 구현하고 action ID 와 float 파리미터를 저장한다.
반환된 NavDirections 객체는 navigate() 에 넘길 수 있다.
@Override
public void onClick(View view) {
float amount = 10.12f;
SpecifyAmountFragmentDirections.ActionSpecifyAmountFragmentToConfirmationFragment action =
SpecifyAmountFragmentDirections.actionSpecifyAmountFragmentToConfirmationFragment(amount);
Navigation.findNavController(view).navigate(action);
}
Navigate using ID
# navigate(int) 는 action ID 나 destination ID 를 받는다.
# action ID 로 navigate 해야 safe args 를 사용할 수 있고 animate transition 이 가능하다.
# 버튼의 경우, Navigation 클래스는 createNavigateOnClickListener() 편리한 메소드를 제공한다.
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null));
Provide navigation options to actions
# When you define an action in the navigation graph, Navigation generates a corresponding NavAction class.
Destination : The resource ID of the target destination.
Default arguments
Navigation options : (NavOptions) 구성 또는 애니메이션 등의 설정 값
<?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/a">
<fragment android:id="@+id/a"
android:name="com.example.myapplication.FragmentA"
android:label="a"
tools:layout="@layout/a">
<action android:id="@+id/action_a_to_b"
app:destination="@id/b"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
</fragment>
<fragment android:id="@+id/b"
android:name="com.example.myapplication.FragmentB"
android:label="b"
tools:layout="@layout/b">
<action android:id="@+id/action_b_to_a"
app:destination="@id/a"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
Navigate using DeepLinkRequest
NavDeepLinkRequest request = NavDeepLinkRequest.Builder
.fromUri(Uri.parse("android-app://androidx.navigation.app/profile"))
.build()
NavHostFragment.findNavController(this).navigate(request)
Navigation and the back stack
Tapping Up or Back calls the NavController.navigateUp() and NavController.popBackStack() methods, respectively, to remove (or pop) the top destination off of the stack.
NavController.popBackStack() returns a boolean indicating whether it successfully popped back to another destination. The most common case when this returns false is when you manually pop the start destination of your graph.
When the method returns false, NavController.getCurrentDestination() returns null.
...
if (!navController.popBackStack()) {
// Call finish() on your Activity
finish();
}
popUpTo and popUpToInclusive
<fragment
android:id="@+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="@layout/fragment_c">
<action
android:id="@+id/action_c_to_a"
app:destination="@id/a"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
</fragment>
After reaching destination C, the back stack contains one instance of each destination (A, B, C). When navigating back to destination A, we also popUpTo A, which means that we remove B and C from the stack while navigating. With app:popUpToInclusive=”true”, we also pop that first A off of the stack, effectively clearing it. Notice here that if you don’t use app:popUpToInclusive, your back stack would contain two instances of destination A.
Conditional navigation