Navigation
Navigation은 Android Jetpack 구성요소 중 하나로 사용자의 상호작용에 따라 화면 간의 이동을 구현하는데 도움을 줍니다.
이는 FragmentManager를 내부적으로 사용하여 구현되어 있기 때문에 해당 라이브러리를 사용하면 FragmentManager와 상호작용하지 않아도 됩니다.
또한, Navigation을 활용하면 Single Activity Architecture 구조를 보다 쉽게 설계할 수 있습니다.
여기서 SAA는 하나의 Activity와 여러 개의 Fragment를 사용하는 구조입니다.
SAA를 사용하면 다음과 같은 장점과 단점이 있습니다.
- 장점
- Activity는 Fragment에 비하여 상대적으로 무겁기 때문에 메모리나 속도 측면에서 훨씬 더 이득을 취할 수 있습니다.
- 비즈니스 로직을 Fragment 단위로 분리하여 의존성을 줄일 수 있고, Activity보다 유연한 UI 디자인을 지원합니다.
- 단점
- Lifecycle이 Activity 하나만 있을 때에 비하여 더 복잡해집니다.
- Fragment 간의 동작이 비동기로 처리되기 때문에 동기/비동기에 따른 이슈가 발생할 수 있습니다.
Navigation의 구성 요소
앱을 탐색할 때에는 Navigation Graph의 경로를 따라 탐색하거나 특별히 지정된 destination으로 탐색할 것이라고 NavController에게 알립니다. 그러면 NavController는 NavHost에게 적절한 destination을 표시합니다.
- Navigation Graph : 모든 Navigation 관련 정보를 포함하는 XML 리소스 파일입니다. 이 파일에는 destination(프래그먼트 또는 액티비티)라고 불리는 콘텐츠 영역과 destination들의 사이를 연결시켜주는 action(경로) 등이 포함됩니다.
- NavHost : Navigation Graph로부터 destination을 보여주는 빈 컨테이너입니다. 즉, destination이 나타나고 사라지는 컨테이너에 해당합니다.
- NavController : NavHost 내에서 Navigation을 관리하는 객체입니다. NavController는 사용자가 앱 내에서 이동할 때 NavHost의 컨텐츠를 조정합니다. 하나의 NavHost에는 하나의 NavController가 존재하고 있습니다.
Navigation 구현하기
1. 의존성 추가
Navigation을 사용하기 위해서 build.gradle에 Navigation 라이브러리를 추가합니다.
또한, destination을 따라서 인자를 안전하게 전달하도록 도와주는 Safe Args 플러그인을 추가합니다.
// build.gradle (Project)
plugins {
id 'androidx.navigation.safeargs.kotlin' version '2.5.3' apply false
}
// build.gradle (Module)
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:2.5.3"
implementation "androidx.navigation:navigation-ui-ktx:2.5.3"
}
2. NavHost 생성
Fragment의 컨테이너 역할을 하는 FragmentContainerView를 선언하고 여러 속성을 추가합니다.
또한, Fragment와 연결할 BottomNavigaionView를 추가합니다.
- android:name : NavHost를 구현하는 클래스의 이름으로 NavHostFragment을 추가합니다.
- app:defaultNavHost : 해당 속성을 true로 설정하면 뒤로 가기를 눌렀을 때 이전 화면(destination)으로 전환됩니다. 만약 false로 설정한다면 뒤로 가기를 눌러도 이전 화면이 나타나지 않습니다. 즉, 백스택에 destination들을 추가하는 속성입니다.
- app:navGraph : Navigaion Graph 파일을 추가합니다.
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/menu_main" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- menu_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/firstFragment"
android:icon="@drawable/ic_first"
android:title="First" />
<item
android:id="@+id/secondFragment"
android:icon="@drawable/ic_second"
android:title="Second" />
<item
android:id="@+id/thirdFragment"
android:icon="@drawable/ic_third"
android:title="Third" />
</menu>
3. Navigation Graph 생성
res 디렉토리 안에 navigation 폴더를 만든 후 Navigation Graph를 생성합니다.
아래 xml에서 destination은 FirstFragment, SecondFragment, ThirdFragment로 총 3개 있습니다.
또한, FirstFragment -> SecondFragment , SecondFragment -> ThirdFragment로 action이 연결되어 있습니다.
여기서 SecondFragment -> ThirdFragment 사이에서는 Safe Args를 통해 데이터를 주고받고 있습니다.
- Destination(목적지) : 프래그먼트 또는 액티비티에 해당합니다. 프래그먼트 같은 경우는 액티비티 위에 생성되기에 destination으로 넣을 때 아무런 조건이 붙지 않습니다. 하지만 액티비티를 destination으로 넣을 경우에는, 현재 Navigation Graph의 endpoint로 설정한다는 뜻이 됩니다. 즉, 현재 Navigation Graph는 더 이상 사용하지 못하고 새로운 액티비티에 연결된 Navigation Graph로 이동합니다.
- Action(경로) : destination 사이를 연결할 때 사용합니다. 여기에는 다음의 옵션이 있습니다.
- To Destination : 현재 화면에서 다른 화면으로 이동하는 액션
- To Self : 자기 자신으로 이동하는 액션
- Return To Source : popBackStack 시 사용될 액션
- Global : 어떤 화면에서든 현재 화면으로 이동할 때의 액션 (전역적)
<?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/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="(...)FirstFragment"
android:label="FirstFragment"
tools:layout="@layout/fragment_first">
<action
android:id="@+id/action_first_to_second"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="(...)SecondFragment"
android:label="SecondFragment"
tools:layout="@layout/fragment_second">
<action
android:id="@+id/action_second_to_third"
app:destination="@id/thirdFragment" />
</fragment>
<fragment
android:id="@+id/thirdFragment"
android:name="(...)ThirdFragment"
android:label="ThirdFragment">
<argument
android:name="message"
android:defaultValue="No Message"
app:argType="string" />
</fragment>
</navigation>
4. NavController 생성
NavController는 NavHost 내에서 전반적인 탐색을 관리합니다.
각 NavHost는 하나의 NavController를 가지고 있기에 직접 생성할 필요 없이 다음의 메소드를 사용하여 획득할 수 있습니다.
// NavController 획득
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
val navController = navHostFragment.navController
// bottomNavigationView 활성화
binding.bottomNavigationView.setupWithNavController(navController)
또한, NavController를 통해 다음과 같이 action을 활성화할 수 있습니다.
// FirstFragment -> SecondFragment 사이의 action 활성화
binding.btnMove1.setOnClickListener {
val action = FirstFragmentDirections.actionFirstToSecond()
findNavController().navigate(action)
}
Sage Args를 통해 데이터를 주고받는 코드는 다음과 같습니다.
// SecondFragment
// action에 데이터 넣어서 전송
binding.btnMove2.setOnClickListener {
val action = FirstFragmentDirections.actionSecondToThird()
action.message = etMessage.text.toString()
findNavController().navigate(action)
}
// ThirdFragment
// action에서 데이터 수신
private val args: ThirdFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val message = args.message
}
'안드로이드 > UI' 카테고리의 다른 글
[Android] AppBarLayout 사용하기 (0) | 2023.10.04 |
---|---|
[Android] MotionLayout 사용하기 (0) | 2023.10.01 |
[Android] CoordinatorLayout과 Bottom Sheet (0) | 2023.09.19 |
[Android] SearchView (0) | 2023.09.15 |
[Android] CardView (0) | 2023.08.22 |