삽질도사

[안드로이드] jetpack navigation + BottomNavigation 뒤로가기로 홈 화면 가기 본문

안드로이드

[안드로이드] jetpack navigation + BottomNavigation 뒤로가기로 홈 화면 가기

전성진블로그 2023. 4. 26. 10:37

SAA 구조를 사용한 형태에서

스플래시 화면 -> (로그인 및 기타 화면) -> 홈화면 -> 다른 화면 -> (뒤로가기 버튼 누름) -> 홈화면 

일반적인 경우 이런 비슷한 구조를 채택할텐데, SAA에서 popUpTo 혹은 PopUpInclusive를 알고 사용한다 하더라도 
쉽게 떠오르거나 해결되지 않아서(개인적으로..) 매번 생각하느라 고생고생을 한다.

그래서 이러한 구조를 다른 프로젝트에 사용한다던지 혹은 다른 방식으로 구조를 짜더라도 유연하게 생각하기 위해서 이러한 일련의 과정을 기록하기로 했다. 

 

 

 

package kr.foorun.uni_eat.feature.main

import androidx.activity.viewModels
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import dagger.hilt.android.AndroidEntryPoint
import kr.foorun.presentation.MainNavDirections
import kr.foorun.presentation.R
import kr.foorun.presentation.databinding.ActivityMainBinding
import kr.foorun.uni_eat.base.view.base.context_view.BaseActivity

@AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>({ActivityMainBinding.inflate(it)}){

    override val activityViewModel: MainViewModel by viewModels()
    private lateinit var navController: NavController

    override fun afterBinding() = binding {
        setUpBottomNavigationView()
    }

    override fun observeAndInitViewModel() = binding {
        viewModel = activityViewModel.apply {
        }
    }

    private fun setUpBottomNavigationView() = binding {
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
        navController = navHostFragment.navController
        setDestinationListener()
        setUpBottomNav()
    }

    /**
     * @setupWithNavController(navController) is to fetch view into frame when click bottom icon
     *
     * @setOnItemSelectedListener is to make sure action works properly,
     * if you use only setupWithNavController, view is not attached on frame when click bottom icon.
     */
    private fun setUpBottomNav() = binding {
        bottomNav.setupWithNavController(navController) //to fetch view into frame when click bottom icon

        bottomNav.setOnItemSelectedListener {//to make sure action works properly if you use
            when(it.itemId){
                R.id.home_nav -> true.apply { navigate(MainNavDirections.actionToHomeNav()) }
                R.id.map_nav -> true.apply { navigate(MainNavDirections.actionToMapNav()) }
                R.id.event_nav -> true.apply { navigate(MainNavDirections.actionToEventNav()) }
                R.id.article_nav -> true.apply { navigate(MainNavDirections.actionToArticleNav()) }
                R.id.my_nav -> true.apply { navigate(MainNavDirections.actionToMyNav()) }
                else -> false
            }
        }
    }

    private fun navigate(directions: NavDirections) = navController.navigate(directions)

    private fun setDestinationListener() = navController.addOnDestinationChangedListener { controller, destination, arg ->
        if(arg != null){
            if (arg.isEmpty) bottomVisible(true)
            else if(arg.getBoolean(getString(R.string.hide_bottom))) bottomVisible(false)
        } else bottomVisible(true)
    }

    fun bottomVisible(isVisible: Boolean) = activityViewModel.setBottomVisible(isVisible)
}

네비게이션 구조

<?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/main_nav"
    app:startDestination="@id/splash_nav">

    <include app:graph="@navigation/article_nav" />
    <include app:graph="@navigation/event_nav" />
    <include app:graph="@navigation/my_nav" />
    <include app:graph="@navigation/map_nav" />
    <include app:graph="@navigation/login_nav" />
    <include app:graph="@navigation/home_nav" />

    <fragment
        tools:layout="@layout/fragment_article_detail"
        android:id="@+id/article_detail_fragment"
        android:name="kr.foorun.uni_eat.feature.article.detail.ArticleDetailFragment"
        android:label="ArticleDetailFragment" >
        <argument
            android:name="@string/hide_bottom"
            app:argType="boolean"
            android:defaultValue="true" />
    </fragment>

    <action
        app:enterAnim="@anim/from_right"
        app:exitAnim="@anim/to_left"
        app:popUpToInclusive="true"
        app:popUpTo="@id/splash_fragment"
        android:id="@+id/action_to_loginFragment"
        app:destination="@id/login_nav" />

    <action
        android:id="@+id/action_to_articleDetailFragment"
        app:destination="@id/article_detail_fragment" />

    <action
        android:id="@+id/action_to_my_nav"
        app:popUpTo="@id/home_fragment"
        app:popUpToInclusive="false" //bottom navigation에 있는 경우 이것을 false로 해서 home만 남게한다.
        app:destination="@id/my_nav" />

    <action
        android:id="@+id/action_to_map_nav"
        app:popUpTo="@id/home_fragment"
        app:popUpToInclusive="false" //bottom navigation에 있는 경우 이것을 false로 해서 home만 남게한다.
        app:destination="@id/map_nav" />

    <action
        android:id="@+id/action_to_event_nav"
        app:popUpTo="@id/home_fragment"
        app:popUpToInclusive="false" //bottom navigation에 있는 경우 이것을 false로 해서 home만 남게한다.
        app:destination="@id/event_nav" />

    <action
        android:id="@+id/action_to_article_nav"
        app:popUpTo="@id/home_fragment"
        app:popUpToInclusive="false" //bottom navigation에 있는 경우 이것을 false로 해서 home만 남게한다.
        app:destination="@id/article_nav" />

    <action
        app:popUpToInclusive="true"
        app:popUpTo="@id/login_fragment"
        android:id="@+id/action_to_home_nav"
        app:destination="@id/home_nav" />

    <include app:graph="@navigation/splash_nav" />
</navigation>

main_nav의 코드인데 이곳에서 액션/화면 을 정의하여 글로벌하게 쓰고 있다. 

중요한건 위 코드에서 주석처리한 것처럼 액션을 사용해서 stack에 HomeFragment만 남게 하는 것이 목적이다.

이렇게 사용함으로써 다른 화면(bottom navigation 에 존재하는)에서 뒤로가기를 누르더라도 영상처럼 홈화면으로 갈 수 있다.