Il y a deux ans, à Google I / O, les développeurs Android se sont vus présenter une nouvelle solution de navigation dans les applications: la bibliothèque Jetpack Navigation Component
. On en a déjà assez dit sur les petites applications, mais il n'y a Navigation Component
pratiquement aucune information sur les problèmes que vous pourriez rencontrer lors de la traduction d'une grande application en .
, , Navigation Component
Android-.
Android 11 Android Academy. , . – .
, BottomNavigationView
, – , – , , . — , , , , .
Disclaimer
, hh.ru, , . , , , .
:
, , , .
BottomNavigationView
- Navigation Component
, : BottomNavigationView
Google back stack- . , , .
— , . , , .
Android Studio 4.1 Beta
( - ) . .
-
Activity
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
«» , .
ConstraintLayout
, BottomNavigationView
<fragment>
NavHostFragment
- (Android Studio
, , , FragmentContainerView
).
-
BottomNavigationView
<navigation
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="com.aaglobal.graph_example.ui.home.HomeFragment"/>
<fragment
android:id="@+id/navigation_dashboard"
android:name="com.aaglobal.graph_example.ui.dashboard.DashboardFragment"/>
<fragment
android:id="@+id/navigation_notifications"
android:name="com.aaglobal.graph_example.ui.notifications.NotificationsFragment"/>
</navigation>
destination- .
- -
BottomNavigationView
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
, destination- . BottomNavigationView
, , .
.
Dashboard
ViewModel
. , Home
Dashboard
, . Home
Dashboard
. .
Issue Tracker-. , Google- Fragment
-, back stack- FragmentManager
-. Medium Ian Lake, , Google , , , BottomNavigationView
.
– . , BottomNavigationView, . , , .
Dashboard
, . , Dashboard
, , Graphic
. Back
– , . , Graphic
, Dashboard
, , .
« », – . .
workaround
Google- Architecture Components
, NavigationAdvancedSample.
NavigationExtensions.kt. , , , .
- -, ,
<?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/navigation_home"
app:startDestination="@id/HomeFragment">
<fragment
android:id="@+id/HomeFragment"
android:name="com.aaglobal.jnc_playground.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
</navigation>
, BottomNavigationView
XML, startDestination
.
- -,
NavHostFragment
,
private fun obtainNavHostFragment(
fragmentManager: FragmentManager,
fragmentTag: String,
navGraphId: Int,
containerId: Int
): NavHostFragment {
// If the Nav Host fragment exists, return it
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
existingFragment?.let { return it }
// Otherwise, create it and return it.
val navHostFragment = NavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
.add(containerId, navHostFragment, fragmentTag)
.commitNow()
return navHostFragment
}
FragmentManager back stack- , , back stack. NavHostFragment
- . , BottomNavigationView
NavController
.
- -,
BottomNavigationView
listener
, back stack-
setOnNavigationItemSelectedListener { item ->
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
if (selectedItemTag != newlySelectedItemTag) {
fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE)
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
if (firstFragmentTag != newlySelectedItemTag) {
fragmentManager.beginTransaction()
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment).apply {
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit()
}
selectedNavController.value = selectedFragment.navController
true
} else {
false
}
}
, BottomNavigationView
FragmentManager
-, . , back stack-.
-
BottomNavigationView
LiveData
,NavController
.NavController
, ,ActionBar
-
class RootActivity : AppCompatActivity(R.layout.activity_root) {
private var currentNavController: LiveData<NavController>? = null
private fun setupBottomNavigationBar() {
// Setup the bottom navigation view with a list of navigation graphs
val liveData = bottom_nav.setupWithNavController(
navGraphIds = listOf(
R.navigation.home_nav_graph,
R.navigation.dashboard_nav_graph,
R.navigation.notifications_nav_graph
),
fragmentManager = supportFragmentManager,
containerId = R.id.nav_host_container,
intent = intent
)
// Whenever the selected controller changes, setup the action bar.
liveData.observe(this, Observer { ctrl -> setupActionBarWithNavController(ctrl) })
currentNavController = liveData
}
}
BottomNavigationView
onCreate
-, Activity
, onRestoreInstanceState
, Activity
.
, , , BottomNavigationView
, .
, , .
workaround- – .
:
– :
workaround-
, workaround , . NavigationAdvancedSample Activity, .
:
, Splash-:
Google , Splash- – , UX . , Splash- – Android-. Single Activity
-, Fragment
, Activity
:
BottomNavigationView
:
class MainFragment : Fragment(R.layout.fragment_main) {
private var currentNavController: LiveData<NavController>? = null
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
setupBottomNavigationBar()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState == null) {
setupBottomNavigationBar()
}
}
}
Splash- BottomNavigationView
. hh.ru, ActionBar
.
Theme.MaterialComponents.DayNight.DarkActionBar
Theme.MaterialComponents.DayNight.NoActionBar
NavController
- ActionBar
-:
class MainFragment : Fragment(R.layout.fragment_main) {
private var currentNavController: LiveData<NavController>? = null
private fun setupBottomNavigationBar() {
val navGraphIds = listOf(
R.navigation.search__nav_graph,
R.navigation.favorites__nav_graph,
R.navigation.responses__nav_graph,
R.navigation.profile__nav_graph
)
val controller = bottom_navigation.setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = requireActivity().supportFragmentManager,
containerId = R.id.fragment_main__nav_host_container,
intent = requireActivity().intent
)
currentNavController = controller
}
}
, , Splash- . .
? onDestroyView
NavHostFragment
NavController
-. - NavController
, LiveData
, Navigation.findNavController
onDestroyView
.
NavController
- ( Navigation Component- Navigation.setViewNavController
), .
class MainFragment : Fragment(R.layout.fragment_main) {
private var currentNavController: LiveData<NavController>? = null
private fun setupBottomNavigationBar() {
...
currentNavController?.observe(
viewLifecycleOwner,
Observer { liveDataController ->
Navigation.setViewNavController(requireView(), liveDataController)
}
)
}
}
. Don't keep activities, , . , – IllegalStateException
FragmentManager
– FragmentManager already executing transactions
.
, , , .
, NavHostFragment
FragmentManager
- . : attach-detach Handler.post {}
.
// NavigationExtensions.kt
private fun attachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment,
isPrimaryNavFragment: Boolean
) {
Handler().post {
fragmentManager.beginTransaction()
.attach(navHostFragment)
.apply {
if (isPrimaryNavFragment) {
setPrimaryNavigationFragment(navHostFragment)
}
}
.commitNow()
}
}
Handler.post
, .
BottomNavigationView
-
BottomNavigationView
Navigation Component
, , workaround-. -
BottomNavigationView
, , .