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.
:
: 4 — A, B, C D. A B C, C D, D — , C->D.
, , .
? «» 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
-, , .
.
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- :
<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 - , .
// 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
. , , :
// 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», , . IllegalArgumentException
– NavController
, , 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
, , :
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
}
}
- – .
- , .