[navigation] FragmentContainerView의 navGraph를 교체하고 처음 띄울 fragment(startDestination)을 제어하기

Hanseul Lee·2023년 6월 16일
0

Jetpack을 공부하자!

목록 보기
5/5

제목과 같이 navigation 라이브러리를 활용하며 FragmentContainerView를 쓰고 있는데 navigation graph과 startDestination를 갈아끼워야 했다. 우선 의도대로 동작하는 줄 알았던 코드를 보자.

되는 줄 알았지만 문제인 코드

override fun initView() {
        super.initView()
        setNavigation()
        initNavigation()
        setNavigationBackListener()
    }

    private fun setNavigation() {
        val navHostFragment =
            supportFragmentManager.findFragmentById(binding.commonDetailHostFragment.id) as NavHostFragment
        navController = navHostFragment.navController
    }

    private fun initNavigation() {
        val initialFragment = getInitialFragment()
        val navigation = intent.getIntExtra("navigationId", R.navigation.my_detail_nav_graph)
        val navGraph = navController.navInflater.inflate(navigation)

        navGraph.setStartDestination(initialFragment.id)
        navController.graph = navGraph
    }

    private fun getInitialFragment(): NavDestination {
        val fragmentId = intent.getIntExtra("fragmentId", R.id.membershipFragment)
        return navController.graph.findNode(fragmentId)
            ?: navController.graph.findStartDestination()
    }

    private fun setNavigationBackListener() {
        binding.commonDetailToolbar.detailBacKBtn.setOnClickListener {
            if (navController.backQueue.size == 2) {
                finish()
            } else {
                navController.popBackStack()
            }
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }

intent로 특정 값을 받아오고, 그 값에 따라서 FragmentContainerView에 navigation graph를 갈아끼우고, 또 그 그래프에서 처음 띄울 fragment(startDestination)을 제어하는 코드다.
잘 동작하는 줄 알았다. 이 코드의 default 값인 navigation graph를 끼워넣을 때는..

시도한 코드

그러나 위 코드는 다양한 navigation graph를 넣었을 때

💡 java.lang.RuntimeException: Unable to start activity ComponentInfo{~~~~~~~.widget.CommonDetailActivity}: java.lang.IllegalArgumentException: navigation destination 2131230843 is not a direct child of this NavGraph

이런 오류가 뜨면서 제대로 동작하지 않는다. navigation graph가 제대로 적용되지 않았기 때문.

그래서 아래처럼 네비게이션 초기화 함수를 바꿔봤는데 무조건 startDestination을 반환했다.

private fun initNavigation() {
        val navigation = intent.getIntExtra("navigationId", R.navigation.my_detail_nav_graph)
        val navGraph = navController.navInflater.inflate(navigation)
        navController.graph = navGraph
        val initialFragment = getInitialFragment()

        navGraph.setStartDestination(initialFragment.id)
    }

최종 해결 코드

  1. 최초로 띄울 프래그먼트의 아이디를 받아오고
  2. 적용할 navGraph도 받아옴
  3. navController에 받아온 navGraph를 inflate함
  4. startDestination을 받아온 프래그먼트 아이디로 설정함
  5. 설정한 startDestination을 받아온 navGraph에 적용함
  6. 최종 커스텀된 navGraph를 navController에 선언함
private fun initNavigation() {
        val initialFragmentId = intent.getIntExtra("fragmentId", R.id.membershipFragment)
        val navigation = intent.getIntExtra("navigationId", R.navigation.my_detail_nav_graph)
        val navGraph = navController.navInflater.inflate(navigation)
        val startDestination = getStartDestination(navGraph, initialFragmentId)

        navGraph.setStartDestination(startDestination)
        navController.graph = navGraph
    }
    
    private fun getStartDestination(navGraph: NavGraph, fragmentId: Int): Int {
        return navGraph.findNode(fragmentId)?.id ?: navGraph.findStartDestination().id
    }

그냥.. 코딩 아무 생각 없이 해서 생긴 문제^^;

전체 코드

공통 액티비티로 활용하고 재활용할 수 있게 만든 코드라서 전체 코드도 올려둔다. navController와 같이 선언되지 않은 것들은 BaseAcitivity에 있다~

@AndroidEntryPoint
class CommonDetailActivity :
    BaseActivity<ActivityCommonDetailBinding>(ActivityCommonDetailBinding::inflate) {

    private val toolbarStateViewModel: ToolbarStateViewModel by viewModels()

    override fun initView() {
        super.initView()
        setNavigation()
        initNavigation()
        setNavigationBackListener()
        this.onBackPressedDispatcher.addCallback(this, backPressedCallback)
    }

    private fun setNavigation() {
        val navHostFragment =
            supportFragmentManager.findFragmentById(binding.commonDetailHostFragment.id) as NavHostFragment
        navController = navHostFragment.navController
    }

    private fun initNavigation() {
        val initialFragmentId = intent.getIntExtra("fragmentId", R.id.membershipFragment)
        val navigation = intent.getIntExtra("navigationId", R.navigation.my_detail_nav_graph)
        val navGraph = navController.navInflater.inflate(navigation)
        val startDestination = getStartDestination(navGraph, initialFragmentId)

        navGraph.setStartDestination(startDestination)
        navController.graph = navGraph
    }

    private fun getStartDestination(navGraph: NavGraph, fragmentId: Int): Int {
        return navGraph.findNode(fragmentId)?.id ?: navGraph.findStartDestination().id
    }

    private fun setNavigationBackListener() {
        binding.commonDetailToolbar.detailBacKBtn.setOnClickListener {
            setBackInteraction()
        }
    }

    private val backPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            setBackInteraction()
        }
    }

    private fun setBackInteraction() {
        if (navController.backQueue.size == 2) {
            finish()
        } else {
            navController.popBackStack()
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }

    override fun setObserver() {
        super.setObserver()
        toolbarStateViewModel.detailToolbarTitle.observe(this) { title ->
            binding.commonDetailToolbar.detailTitle.text = title
        }
    }

}

0개의 댓글