Why use Jetpack Navigation
- One activity/theme to rule them all: each screen will be either a fragment or dialog
- Auto handling of fragment transactions, with support for transition and animation
- Easy integration with BottomNavigationView
- Safe Args - easily pass data between destinations (fragment/dialog)
- Ensures a consistent and predictable user experience by adhering to an established set of principles.
Caveats: Android Jetpack Navigation Fragment Lost State After Navigation
Edit project build.gradle
.
buildscript {
ext {
...
material_version = '1.0.0'
nav_version = '2.1.0'
}
...
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
Edit module:app build.gradle
.
...
// apply plugin: "androidx.navigation.safeargs"
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
...
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
...
// Material
implementation "com.google.android.material:material:$material_version"
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
Edit gradle.properties
android.useAndroidX=true
MainActivity.kt
class MainActivity : AppCompatActivity() { private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) setSupportActionBar(toolbar) navController = findNavController(R.id.nav_host_fragment) val appBarConfig = AppBarConfiguration() setupActionBarWithNavController(navController, appBarConfig) bottomNavView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation // return navController.navigateUp(drawerLayout) return navController.navigateUp() }}
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
</LinearLayout>
Navigation: res/navigation/mobile_navigation.xml
navigate_home -> flow_step1(name) -> flow_step2(age) -> navigate_home
<?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/mobile_navigation"
app:startDestination="@id/navigate_home">
<fragment
android:id="@+id/navigate_home"
android:name="com.luasoftware.bottomnavigationexample.view.HomeFragment"
android:label="Home"
tools:layout="@layout/home" >
<action
android:id="@+id/action_navigate_home_to_flow_step1"
app:destination="@id/flow_step1" />
</fragment>
<fragment
android:id="@+id/flow_step1"
android:name="com.luasoftware.bottomnavigationexample.view.flow.Step1FlowFragment"
android:label="step1_flow"
tools:layout="@layout/step1_flow" >
<action
android:id="@+id/action_flow_step1_to_flow_step2"
app:destination="@+id/flow_step2" />
<argument
android:name="name"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/flow_step2"
android:name="com.luasoftware.bottomnavigationexample.view.flow.Step2FlowFragment"
android:label="step2_flow"
tools:layout="@layout/step2_flow" >
<argument
android:name="age"
app:argType="integer" />
<action
android:id="@+id/action_flow_step2_to_navigate_home"
app:destination="@id/navigate_home" />
</fragment>
</navigation>
Fragments
Home
class HomeFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.home, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) startFlowButton.setOnClickListener { // findNavController().navigate(R.id.flow_step1, null) val name = "Desmond" val action = HomeFragmentDirections.actionNavigateHomeToFlowStep1(name) findNavController().navigate(action) } }}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.HomeFragment">
<Button
android:id="@+id/startFlowButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Flow" />
</LinearLayout>
Step1Flow
class Step1FlowFragment : Fragment() { val args: Step1FlowFragmentArgs by navArgs() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.step1_flow, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) statusTextView.text = args.name nextFlowButton.setOnClickListener { val age = 40 val action = Step1FlowFragmentDirections.actionFlowStep1ToFlowStep2(age) findNavController().navigate(action) } }}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".view.flow.Step1FlowFragment">
<TextView
android:id="@+id/statusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Step1 Flow" />
<Button
android:id="@+id/nextFlowButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next Flow" />
</LinearLayout>
Step2Flow
class Step2FlowFragment : Fragment() { val args: Step2FlowFragmentArgs by navArgs() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.step2_flow, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) statusTextView.text = args.age.toString() endFlowButton.setOnClickListener { val action = Step2FlowFragmentDirections.actionFlowStep2ToNavigateHome() findNavController().navigate(action) } }}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".view.flow.Step2FlowFragment">
<TextView
android:id="@+id/statusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Step1 Flow" />
<Button
android:id="@+id/endFlowButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="End Flow" />
</LinearLayout>
NOTE: Refer Android Setup BottomNavigationView With Jetpack Navigation UI (Kotlin).
Proguard
-keepnames class com.path.to.your.ParcelableArg
-keepnames class com.path.to.your.SerializableArg
-keepnames class com.path.to.your.EnumArg
References: