This mimicks what you can achieve in iOS by using a root UITabBarController
with a UINavigationController
for each section.
When you switch to a different section and then you come back, you will be exactly at the last position you were in that section.
The solution described here is a temporary workaround with code taken from NavigationAdvancedSample, while waiting for JetPack Navigation official multiple backstack support. Check this tweet
IMHO it's better than a fully handmade custom solution from scratch without JetPack Navigation:
- you can use nav graphs, view models, safe args, deep links
- looking at mid/long range period in a project's maintenance, this is going in the same direction as JetPack
however take this as a study and experimental material, being sure you understand it fully before applying it to a real production project.
I followed the principle of:
Write code that's easy to delete, not easy to extend
since this will be useless once official support in JetPack Navigation is implemented and you want to keep your codebase as clean as possible.
USE THIS AT YOUR OWN RISK. FEEL FREE TO COMMENT IF YOU HAVE SUGGESTIONS FOR IMPROVEMENT OR TO REPORT SOME REAL SCENARIOS WHERE YOU USED IT AND THE OUTCOMES, WHICH CAN SERVE ALSO AS FEEDBACK TO PEOPLE WORKING ON JETPACK NAVIGATION.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Create a new project or open an existing one. You need the following dependencies:
def lifecycle_version = "2.2.0-rc03"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
def nav_version = "2.2.0-rc04"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Support
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
implementation "com.google.android.material:material:1.1.0-rc01"
Copy:
NavigationBottomBarSectionsStateKeeperWorkaround.kt
NavigationWorkaroundExtensions.kt
-> import your project's R to resolve animations imports
in your project (both files are in this gist after the README).
In your activity layout you need to have:
androidx.fragment.app.FragmentContainerView
com.google.android.material.bottomnavigation.BottomNavigationView
Example:
activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:theme="@style/AppTheme.AppBarOverlay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
app:layout_constraintTop_toBottomOf="@id/appbar" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:menu="@menu/main_bottombar" />
</androidx.constraintlayout.widget.ConstraintLayout>
Then, create the menu which contains the bottom bar items. Example:
main_bottombar.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_section1"
android:icon="@android:drawable/ic_menu_call"
android:title="Section 1" />
<item
android:id="@+id/nav_section2"
android:icon="@android:drawable/ic_menu_add"
android:title="Section 2" />
<item
android:id="@+id/nav_section3"
android:icon="@android:drawable/ic_menu_camera"
android:title="Section 3" />
</menu>
Create one nav graph for each section in the tab bar. In this example, the 3 nav graphs must be called:
nav_section1
nav_section2
nav_section3
because for the link between the bottom bar and the sections to work, main_bottombar menu IDs must match navigation graph IDs
.
Populate each section with your navigation items and don't forget to set every startDestination
.
now in your MainActivity.kt
add:
class MainActivity : AppCompatActivity() {
private val navSectionsStateKeeper by lazy {
NavigationBottomBarSectionsStateKeeperWorkaround(
activity = this,
navHostContainerID = R.id.nav_host_fragment,
navGraphIds = listOf(
R.navigation.nav_section1,
R.navigation.nav_section2,
R.navigation.nav_section3
),
bottomNavigationViewID = R.id.bottom_navigation
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
navSectionsStateKeeper.onCreate(savedInstanceState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
navSectionsStateKeeper.onRestoreInstanceState(savedInstanceState)
}
override fun onSupportNavigateUp() =
navSectionsStateKeeper.onSupportNavigateUp()
override fun onBackPressed() {
if (!navSectionsStateKeeper.onSupportNavigateUp())
super.onBackPressed()
}
}
Run and enjoy 🎉