티스토리 뷰

Navigation Component를 사용하면서 접하게 되는 Usecase는 Splash Screen이다. 왜냐하면 Splash Screen은 앱이 실행될 때 제일 먼저 떴다가 사라져야 하고, 백스택에서 제거되어야 하기 때문이다.

 

네비게이션 그래프를 보자. 아래와 같이 두개의 SplashFragment와 MainFragment가 존재하고 SplashFragment가 시작 Fragment이다.

<?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/splashFragment">

    <fragment
        android:id="@+id/splashFragment"
        android:name="com.mpark.nav.SplashFragment"
        tools:layout="@layout/fragment_splash">
        <action
            android:id="@+id/action_splash_to_main"
            app:destination="@id/mainFragment"/>
    </fragment>
    
    <fragment
        android:id="@+id/mainFragment"
        android:name="com.mpark.nav.mainFragment"
        tools:layout="@layout/fragment_main">
    </fragment>
</navigation>

아래는 SplashFragment 클래스이다. 3초 후에 MainFragment로 이동한다.

class SplashFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_splash, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        showMain()
    }

    private fun showMain() {
    	Handler().postDelayed({
            findNavController().navigate(R.id.action_splash_to_main)
        }, 3000L)
    }
}

이 상태로 앱을 돌려 보면 이상한 점을 발견할 수 있다. MainFragment에서 디바이스에 있는 Back 버튼을 누르면 앱이 종료되지 않는다. (API 29, Android 10 Emulator에서 테스트) 이건 SplashFragment가 시작점인데, 강제로 MainFragment로 이동하면서 BackStack이 꼬였기 때문인 것으로 보인다. BackStack에 실제로는 아무 것도 존재하지 않는데, 강제로 MainFragment로 이동하면서 Navgation Component에 변경사항이 전달되지 않았기 때문이다. 어떻게 하면 백버튼을 눌렀을 때 앱이 정상적으로 종료되도록 할 수 있을까?

해결책은 NavOptions에 있다. NavigationController의 navigate 함수에는 Bundle과 NavOptions은 파라미터로 전달할 수 있다.

findNavController().navigate(R.id.action_splash_to_main,
                null,
                NavOptions.Builder()
                          .setPopUpTo(R.id.nav_graph, false)
                          .build()
        )

NavOptions의 PopUpTo는 BackStack에서 어디까지 이동할 것인지 결정하는 속성이다. 예를 들어 Fragment1, Fragment2, Fragment3이 BackStack에 존재하고 Fragment1 -> Fragment2 -> Fragment3 순으로 호출될 때, Frgament3에서  Back Button을 눌렀을 때 Fragment2로 돌아오지 않고 Fragment1으로 바로 돌아오게 할 경우, Fragment1에 PopupTo를 설정함으로써 원하는 결과를 얻을 수 있다.

setPopupTo의 첫번째 파라미터는 이동하기를 원하는 대상 Fragment의 id고 두번째 파라미터는 첫번째 파라미터의 Fragment까지 포함할 것인지의 여부이다. 기본값은 false이다. 이렇게 NavOptions를 세팅함으로써 NavController에게 SplashFragment가 BackStack에 있으면 SplashFragment를 제거하라고 알려주고 있다. 이렇게 함으로써 MainFragment에서 Back Button을 누를 경우 NavController는  BackStack에 남은 Fragment가 없다는 것을 알고 있으므로, 정상적으로 앱을 종료할 수 있다.

 

코드를 통해 NavOptions를 전달할 수도 있지만, XML을 통해서도 똑같이 할 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<navigation ...>

    <fragment
        ...>
        <action
            android:id="@+id/action_splash_to_main"
            app:destination="@id/mainFragment"
            app:popUpTo="@id/splashFragment"
            app:popUpToInclusive="true"/>
    </fragment>
    
    <fragment... />
</navigation>

위와 같이 app:popUpTo와 app:popUpToInclusive를 설정하면 된다.

 

Edit: Navigation Component를 사용하면서 Splash Screen을 구현하려고 할 때 가장 안전하고 골치가 아프지 않은 방법은 별도의 Activity를 띄워서 처리하는 방법이다. Navigation Component는  SingleActivity 구조 이기 때문에 StartDestination에 해당하는 Fragment를 백스택에서 제거하면 안되기 때문에 SplashFragment를 StartDestination으로 사용할 수 없다. 따라서 HomeFragment를 StartDestination으로 설정해서 사용할 때는 OnBackPressedDispatcher를 이용하여 BackPressed에 대한 처리를 해주어야 해주는 것이 좋다. developer.android.com/guide/navigation/navigation-custom-back

 

맞춤형 뒤로 탐색 기능 제공  |  Android 개발자  |  Android Developers

뒤로 탐색 기능은 사용자가 이전에 방문한 화면 기록을 통해 뒤로 이동하는 기능입니다. 모든 Android 기기는 이 유형의 탐색을 위해 뒤로 버튼을 제공하므로 앱 UI에 뒤로 버튼을 추가하면 안 됩��

developer.android.com

아직까지는 Navigation Component는 기능이 완전하지 않고 부족한 감이 있다. 그리고 navigation 설정을 전부 xml에 넣다 보면 큰 프로젝트의 경우는 xml이 엄청 지저분해 진다. 그래서 가능하면 코드를 통해 제어하는 것이 좀 더 깔끔하고 유지보수하기에 좋다. 개인적으로는 navigation component를 바로 호출하지 않고 Coordinator 패턴을 사용하여 wrapping 된 클래스를 사용하고 있다. 이 부분이 세션처리나 플로우 처리에 훨씬 용이하며, LiveData와 함께 사용할 때 navigation 이벤트가 여러번 호출 되는 것도 방지할 수 있다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함