Navigation Component-Jutsu, vol. 2 - nested navigation graphs



Every large app contains many ways to navigate between screens. A good navigation library should help the developer implement them. It was with this thought that I approached the study of cases with nested navigation graphs.



This is the second of three articles on implementing navigation cases using the Navigation Component.



BottomNavigationView, — , .



?





:





: 4 — A, B, C D. A B C, C D, D — , C->D.



?

, Navigation Component-, BottomNavigationView ( Search Responses – A B):





, (C D):





C Search ( A), D Search:





C Responses, C->D Responses:





, , .



? «»‎ XML- , :



<!-- company_flow__nav_graph.xml -->
<navigation
    android:id="@+id/company_flow__nav_graph"
    app:startDestination="@id/CompanyFragment">

    <fragment
        android:id="@+id/CompanyFragment"
        android:name="ui.company.CompanyFragment">
        <action
            android:id="@+id/action__CompanyFragment__to__CompanyDetailsFragment"
            app:destination="@id/CompanyDetailsFragment" />
    </fragment>

    <fragment
        android:id="@+id/CompanyDetailsFragment"
        android:name="ui.company.CompanyDetailsFragment"/>

</navigation>


action-:



<navigation
    android:id="@+id/menu__search"
    app:startDestination="@id/SearchContainerFragment">

    <fragment
        android:id="@+id/SearchContainerFragment"
        android:name="ui.tabs.search.SearchContainerFragment">

        <action
            android:id="@+id/action__SearchContainerFragment__to__CompanyFlow"
            app:destination="@id/company_flow__nav_graph" />

    </fragment>

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

</navigation>


, . ?



, Navigation Component , . back stack- popBackUp popBackUpInclusive XML, popBackStack NavController-.



, : Splash , mBackStack NavController- Splash- NavBackStackEntry.



?



, , back stack- SplashFragment. ? , NavGraph, Activity, – SplashFragment, FragmentNavigator.Destination.



NavController- popBackStack ? back stack- NavController-, , .



.



flow popBackStack
class CompanyDetailsFragment : Fragment(R.layout.fragment_company_details) {

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

        finish_flow_button.setOnClickListener {
            findNavController().popBackStack(R.id.company_flow__nav_graph, true)
        }
    }

}


: . , action XML- :



action-
<fragment
  android:id="@+id/CompanyDetailsFragment"
  android:name="ui.company.CompanyDetailsFragment"
  android:label="@string/fragment_company_details__title"
  tools:layout="@layout/fragment_company_details">

  <action
      android:id="@+id/action__finishCompanyFlow"
      app:popUpTo="@id/company_flow__nav_graph"
      app:popUpToInclusive="true" />

</fragment>


NavController:



findNavController().navigate(R.id.action__finishCompanyFlow)


- : navigate .





, . : - ?



, . Navigation Component 2.3 Google key-value – SavedStateHandle. NavController- – previousBackStackEntry currentBackStackEntry. Google - , .



SavedStateHandle
// Flow screen
findNavController().previousBackStackEntry
    ?.savedStateHandle
    ?.set("some_key", "value")

// Screen that waits result
val result = findNavController().currentBackStackEntry
    ?.savedStateHandle
    ?.remove<String>("some_key")


, ? previousBackStackEntry SavedStateHandle, . :



fragment_company_details__button.setOnClickListener {
    // Here we are inside nested navigation flow
    findNavController().popBackStack(R.id.company_flow__nav_graph, true)

    // At this line, "findNavController().currentBackStackEntry" means
    // screen that STARTED current nested flow.
    // So we can send the result!
    findNavController().currentBackStackEntry
      ?.savedStateHandle
      ?.set(COMPANY_FLOW_RESULT_FLAG, true)
}


: findNavController().popBackStack , popBackStack – , ! , SavedStateHandle currentBackStackEntry. entry , .



, , , currentBackStackEntry SavedStateHandle. , , :



SavedStateHandle
// Read result from nested navigation flow
val companyFlowResult = findNavController().currentBackStackEntry
    ?.savedStateHandle
    ?.remove<Boolean>(CompanyDetailsFragment.COMPANY_FLOW_RESULT_FLAG)

text__company_flow_result.text = "${companyFlowResult}"




  • , , NavController.popBackStack, .
  • - SavedStateHandle.




, .



– A B. B A, include. , , A B, B.



B – A:





– B:





.





, BottomNavigationView, . , …



? , « , »? , ?



, :





:





. Auth- , , , : Auth- .



auth flow- action :



<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/menu__profile"
    app:startDestination="@id/ProfileContainerFragment">

    <fragment
        android:id="@+id/ProfileContainerFragment"
        android:name="ui.tabs.profile.ProfileContainerFragment">

        <action
            android:id="@+id/action__ProfileContainerFragment__to__AuthFlow"
            app:destination="@id/auth__nav_graph" />

    </fragment>

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

</navigation>


auth- , :





Activity, - / BottomNavigationView. NavigationAdvancedSample , .



? - BottomNavigationView ( , , Host- ), Auth- .



?

, .





action MainFragment- :



<!— app_nav_graph.xml —>
<fragment
  android:id="@+id/SplashFragment"
  android:name="com.aaglobal.jnc_playground.ui.splash.SplashFragment"/>

<fragment
  android:id="@+id/MainFragment"
  android:name="com.aaglobal.jnc_playground.ui.main.MainFragment">
  <action
      android:id="@+id/action__MainFragment__to__AuthFlow"
      app:destination="@id/auth__nav_graph" />
</fragment>

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


, action , :



fragment_profile_container__button__open_auth_flow.setOnClickListener {
    findNavController().navigate(R.id.action__MainFragment__to__AuthFlow)
}


IllegalArgumentException, NavController Host- .



«» NavController



, , «» NavController, action «» ( ) . action , , .



Navigation Component NavController-, , – Navigation.findNavController:



fragment_profile_container__button__open_auth_flow.setOnClickListener {
  Navigation.findNavController(
    requireActivity(),
    R.id.activity_root__fragment__nav_host
  ).navigate(R.id.action__MainFragment__to__AuthFlow)
}




Back



, . : «Back», , . IllegalArgumentExceptionNavController , , NavController .





, :



java.lang.IllegalArgumentException: No view found for id 0x7f08009a (com.aaglobal.jnc_playground:id/fragment_main__nav_host_container) for fragment NavHostFragment{5150965} (e58fc3a2-b046-4c80-9def-9ca40957502d) id=0x7f08009a bottomNavigation#0}


, «Back». AndroidX OnBackPressedCallback. NavController , , :



back- auth-
class StartAuthFragment : Fragment(R.layout.fragment_start_auth) {
    private var callback: OnBackPressedCallback? = null

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

        callback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                Navigation.findNavController(
                    requireActivity(),
                    R.id.activity_root__fragment__nav_host
                ).popBackStack()
            }
        }.also {
            requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, it)
        }
    }
}


callback- , , auth-: «» NavController, , .



! «»: auth-, OnBackPressedCallback =(



, , auth- – «» NavController-:



?
class FinishAuthFragment : Fragment(R.layout.fragment_finish_auth) {

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

      fragment_finish_auth__button.setOnClickListener {
          Navigation.findNavController(
              requireActivity(),
              R.id.activity_root__fragment__nav_host
          ).popBackStack(R.id.auth__nav_graph, true)

          findNavController().currentBackStackEntry
            ?.savedStateHandle
            ?.set(AUTH_FLOW_RESULT_KEY, true)
      }
  }

}




  • , , «» NavController.
  • , .




, Splash-. , . , , , – . , (, ), .





, .



  • «Back» , « » ( Splash), .
  • , .


, , OnBackPressedCallback, , :



StartAuthFragment:



<fragment
  android:id="@+id/StartAuthFragment"
  android:name="com.aaglobal.jnc_playground.ui.auth.StartAuthFragment"
  android:label="Start auth"
  tools:layout="@layout/fragment_start_auth">

  <argument
      android:name="isFromSplashScreen"
      android:defaultValue="false"
      app:argType="boolean"
      app:nullable="false" />

  <action
      android:id="@+id/action__StartAuthFragment__to__FinishAuthFragment"
      app:destination="@id/FinishAuthFragment" />

</fragment>


OnBackPressedCallback:



class StartAuthFragment : Fragment(R.layout.fragment_start_auth) {
    private val args: StartAuthFragmentArgs by navArgs()
    private var callback: OnBackPressedCallback? = null

    private fun getOnBackPressedCallback(): OnBackPressedCallback {
      return object : OnBackPressedCallback(true) {
          override fun handleOnBackPressed() {
              if (args.isFromSplashScreen) {
                  requireActivity().finish()
              } else {
                  Navigation.findNavController(
                    requireActivity(),
                    R.id.activity_root__fragment__nav_host
                  ).popBackStack()
              }
          }
      }
    }
}


Single Activity, requireActivity().finish() , .



. «-».



  • : Navigation Component runtime- , - @id destination- .
  • – , , , Splash.


, destination-, , . runtime- — .



– back stack-, , , . : , , «» ( main auth Splash-, ), , .



– , auth-, , , . SplashFragment-.



auth-:



// FinishAuthFragment.kt

fragment_finish_auth__button.setOnClickListener {
    // Save hasAuthData flag in prefs
    GlobalDI.getAuthRepository().putHasAuthDataFlag(true)

    // Navigate back from auth flow
    Navigation.findNavController(
        requireActivity(),
        R.id.activity_root__fragment__nav_host
    ).popBackStack(R.id.auth__nav_graph, true)

    // Send signal about finishing flow
    findNavController().currentBackStackEntry
      ?.savedStateHandle
      ?.set(AUTH_FLOW_RESULT_KEY, true)
}


SplashFragment-:



// SplashFragment.kt

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

    val authResult = findNavController().currentBackStackEntry
        ?.savedStateHandle
        ?.remove<Boolean>(FinishAuthFragment.AUTH_FLOW_RESULT_KEY) == true

    if (authResult) {
        navigateToMainScreen()
        return
    }
}




  • – .
  • , .


, Navigation Component, , , .




All Articles