Navigation

2022. 4. 19. 20:49Android/AAC

반응형

- 구성요소

  1. Navigation Graph : navigation 정보를 담은 xml 파일
  2. NavHost : Navigation Graph의 목적지를 보일 빈 컨테이너
  3. NavController : NavHost 안에서 navigation을 관리

- Navigation Graph 설계

1. Destination 추가

 1-1. Fragment

 

<?xml version="1.0" encoding="utf-8"?> 
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_graph"
app:startDestination="@id/simpleFragment">
    <Fragment
        android:id="@+id/simpleFragment"
        android:label="@string/sampleFragmentTitle"
        android:name="com.example.android.navigation.fragment.SimpleFragment" />
</navigation>

 

 1-2. DialogFragment

 

<?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/nav_graph">...
    <dialog
        android:id="@+id/my_dialog_fragment"
        android:name="androidx.navigation.myapp.MyDialogFragment">
        <argument
            android:name="myarg"
            android:defaultValue="@null" />
        <action
            android:id="@+id/myaction"
            app:destination="@+id/another_destination" />
    </dialog>
    ...
</navigation>

 

 1-3. Activity : 기본적으로 Navigation Graph는 ActivityScope에서 유효하므로, Activity Destination 추가는 현재 그래프의 endpoint로 사용됨

  - startActivity()와 같은 맥락

 

<?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/navigation_graph"
    app:startDestination="@id/simpleFragment">
    <activity
        android:id="@+id/localDestinationActivity"
        android:label="@string/localActivityTitle"
        app:action="android.intent.action.VIEW"
        app:dataPattern="https://example.com?userId={userId}"
        app:targetPackage="${applicationId}">
        <argument
            android:name="userId"
            app:argType="string" />
    </activity>
</navigation>

  - 암시적 Intent와 같은 맥락 : 찾지 못한 경우, ActivityNotFoundException 발생
   -> 암시적 Intent와 같은 맥락으로 코드에서 발생시킬 경우 아래와 같이 호출할 수 있음

 

navController.navigate(R.id.localDestinationActivity, bundleOf("userId" to "someUser") )
<?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/navigation_graph"
    app:startDestination="@id/simpleFragment">
    <activity
        android:id="@+id/sampleActivityDestination"
        android:name="com.example.android.navigation.activity.DestinationActivity"
        android:label="@string/sampleActivityTitle" />
</navigation>

 

2. Nested Navigation Graph 구현 : Navigation Graph 안에서 다른 Navigation Graph를 취하는 경우로, <navigation>을 통해 직접 기술하거나 <include>를 통해 외부에서 정의한 Navigation Graph xml 파일을 포함시킬수도 있음

 2-1. Global Action 정의 : 모든 Destination에서 사용가능한 action을 재사용할 수 있도록 정의

 

<?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">  <!-- 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
        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" />
    <fragment
        android:id="@+id/game_over"
        android:name="com.example.android.gamemodule.GameOver"
        tools:layout="@layout/fragment_game_over"
        android:label="fragment_game_over" />
</navigation>
<!-- (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>

 

- Navigation

 1. using ID

 

view.findNavController().navigate(R.id.viewTransactionsAction)
findNavController().navigate(
	R.id.action_fragmentOne_to_fragmentTwo,
	null,
	navOptions { // Use the Kotlin DSL for building NavOptions        
	anim {
		enter = android.R.animator.fade_in,
        exit = android.R.animator.fade_out
	}
})

 

2. using Action : 높은 가독성과 전환 animation 적용 가능, compile-time safety를 제공 및 safe args 적용이 가능한 action이 권장됨

 

val action = SpecifyAmountFragmentDirections
	.actionSpecifyAmountFragmentToConfirmationFragment(amount) 
    
v.findNavController().navigate(action)

 

3. using Deeplink 

 

findNavController().navigate(
	deepLinkUri,     
    navOptions { // Use the Kotlin DSL for building NavOptions         
    	anim {             
        enter = android.R.animator.fade_in,
        exit = android.R.animator.fade_out
    }
})

 

4. using DeeplinkRequest : deeplink로 이동하는 경우와 다르게 백스택이 유지됨, popUpTo+popUpToInclusive로 백스택 삭제도 가능

  • popUpToInclusive : true - 해당 destination도 백스택에서 제거, false - 해당 destination까지 백스택 유지
  • popUpToSaveState : true - 백스택 제거 시 state 저장, false - 백스택에서 제거만 진행 
    -> app:restoreSaveState="true"로 해당 destination으로 이동 시 복원(popUpToSaveState가 true여야 의미가 있음)

 

val request = NavDeepLinkRequest.Builder
	.fromUri("android-app://androidx.navigation.app/profile".toUri())     
	.build() 
    
findNavController().navigate(request)

 

5. using Backstack :  NavController.navigateUp() / NavController.popBackStack()
-> FloatingWindnow 인터페이스 상속한 dialog의 경우, 상속하지 않은 fragment나 activity로 navigate 시 모두 popBackStack됨

  • savedStateHandler로 navigation 조건 처리하기

 

class LoginFragment : Fragment() {
    companion object {
        const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL"
    }

    private val userViewModel: UserViewModel by activityViewModels()
    private lateinit var savedStateHandle: SavedStateHandle

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle
        savedStateHandle.set(LOGIN_SUCCESSFUL, false)

        val usernameEditText = view.findViewById(R.id.username_edit_text)
        val passwordEditText = view.findViewById(R.id.password_edit_text)
        val loginButton = view.findViewById(R.id.login_button)

        loginButton.setOnClickListener {
            val username = usernameEditText.text.toString()
            val password = passwordEditText.text.toString()
            login(username, password)
        }
    }

    fun login(username: String, password: String) {
        userViewModel.login(username, password).observe(viewLifecycleOwner, Observer { result ->
            if (result.success) {
                savedStateHandle.set(LOGIN_SUCCESSFUL, true)
                findNavController().popBackStack()
            } else {
                showErrorMessage()
            }
        })
    }

    fun showErrorMessage() {
        // Display a snackbar error message
    }
}

반응형

'Android > AAC' 카테고리의 다른 글

Kotlin Flow  (0) 2022.03.25
Kotlin Coroutine  (0) 2022.03.24
Compose의 State  (0) 2022.03.03
Compose 감잡기  (0) 2022.02.24
Jetpack - Compose  (0) 2021.10.28