├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── vipulasri │ │ └── multiplebackstacknavigation │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vipulasri │ │ │ └── multiplebackstacknavigation │ │ │ ├── MainActivity.kt │ │ │ └── ui │ │ │ ├── BottomNavHistory.kt │ │ │ ├── BottomNavManager.kt │ │ │ ├── dashboard │ │ │ ├── DashboardFragment.kt │ │ │ └── DashboardViewModel.kt │ │ │ ├── home │ │ │ ├── HomeFragment.kt │ │ │ └── HomeViewModel.kt │ │ │ ├── notifications │ │ │ ├── NotificationsFragment.kt │ │ │ └── NotificationsViewModel.kt │ │ │ └── title │ │ │ └── TitleFragment.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_dashboard_black_24dp.xml │ │ ├── ic_home_black_24dp.xml │ │ ├── ic_launcher_background.xml │ │ └── ic_notifications_black_24dp.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_dashboard.xml │ │ ├── fragment_home.xml │ │ ├── fragment_notifications.xml │ │ └── fragment_title.xml │ │ ├── menu │ │ └── bottom_nav_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ ├── mobile_navigation.xml │ │ ├── nav_dashboard.xml │ │ ├── nav_home.xml │ │ └── nav_notifications.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── vipulasri │ └── multiplebackstacknavigation │ └── ExampleUnitTest.kt ├── art └── showcase.gif ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | .idea/ 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | # External native build folder generated in Android Studio 2.2 and later 44 | .externalNativeBuild 45 | 46 | #Android Studio 47 | projectFilesBackup/ 48 | 49 | #DS_STORE 50 | .DS_Store 51 | ._.DS_Store 52 | **/.DS_Store 53 | **/._.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiple Backstack Navigation (Navigation Component) 2 | 3 | As of now [Navigation Component](https://developer.android.com/guide/navigation/navigation-getting-started) doesn't support multiple backstack management out of the box most commonly used in Bottom Navigation. 4 | 5 | Google already has an [Advanced Navigation Sample](https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample) which showcases handling of multiple backstacks. 6 | 7 | **Cons:** 8 | 9 | 1. It always takes the user back to the first tab irrespective for the order they were opened. 10 | 2. Subjective approach, it attaches and detaches the navigation graph each time user navigates through bottom navigation view. 11 | 3. It's difficult to add custom backpress action (ex: saving the data on certain screen before switching tabs) through their implementation. 12 | 13 | ---- 14 | 15 | There are other resources as well to solve these issues, most commonly using ViewPager: [Instagram style navigation using Navigation Component](https://android.jlelse.eu/instagram-style-navigation-using-navigation-component-854037cf1389) 16 | 17 | **Major Issue:** App crashes on configuration change i.e "[no nav controller attached on configuration change](https://github.com/ebi-igweze/ViewPagerNavigation/issues/1)" 18 | 19 | ---- 20 | 21 | **This repository/sample** solves all the issues mentioned above. 22 | 23 | 1. It takes consideration of user actions and navigates use back in the same order. 24 | 25 | Example: 26 | 27 | `User action: Tab 1 -> Tab 2 -> Tab 3 -> Tab 2` 28 | 29 | `Result: Tab 1 -> Tab 3 -> Tab 2` 30 | 31 | 2. In my approach, I am following show/hide of navigation graph, once they are created. While using `Fragment Manager`'s show/hide, it doesn't call `Fragment`'s `onPause()` and `onResume()`. 32 | So, I achieved that by setting their max lifecycle using `SetMaxLifecycle` to have `onPause()` and `onResume()` callbacks. 33 | 34 | 3. I can set custom backpress action before switching tab's navigation. 35 | 4. ***Brownie Point:*** It supports configuration change and restores the from the same state the navigation was on. 36 | 37 | 38 | Preview: 39 | 40 | ![Showcase](art/showcase.gif) 41 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: "androidx.navigation.safeargs" 5 | 6 | android { 7 | compileSdkVersion 30 8 | buildToolsVersion "30.0.0" 9 | 10 | defaultConfig { 11 | applicationId "com.vipulasri.multiplebackstacknavigation" 12 | minSdkVersion 21 13 | targetSdkVersion 30 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation fileTree(dir: "libs", include: ["*.jar"]) 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 38 | implementation 'androidx.core:core-ktx:1.3.0' 39 | implementation 'androidx.appcompat:appcompat:1.1.0' 40 | implementation 'com.google.android.material:material:1.1.0' 41 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 42 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 43 | 44 | //navigation 45 | implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" 46 | implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" 47 | implementation "androidx.navigation:navigation-fragment:$navigation_version" 48 | implementation "androidx.navigation:navigation-ui:$navigation_version" 49 | 50 | //test 51 | testImplementation 'junit:junit:4.12' 52 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 54 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/vipulasri/multiplebackstacknavigation/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.vipulasri.multiplebackstacknavigation", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.vipulasri.multiplebackstacknavigation.ui.BottomNavManager 6 | 7 | class MainActivity : AppCompatActivity() { 8 | 9 | private var bottomNavManager: BottomNavManager? = null 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | 15 | setupNavigationManager() 16 | } 17 | 18 | private fun setupNavigationManager() { 19 | bottomNavManager?.setupNavController() ?: kotlin.run { 20 | bottomNavManager = BottomNavManager( 21 | fragmentManager = supportFragmentManager, 22 | containerId = R.id.nav_host_container, 23 | bottomNavigationView = findViewById(R.id.nav_view) 24 | ) 25 | } 26 | } 27 | 28 | override fun onSaveInstanceState(outState: Bundle) { 29 | super.onSaveInstanceState(outState) 30 | bottomNavManager?.onSaveInstanceState(outState) 31 | } 32 | 33 | override fun onRestoreInstanceState(savedInstanceState: Bundle) { 34 | super.onRestoreInstanceState(savedInstanceState) 35 | bottomNavManager?.onRestoreInstanceState(savedInstanceState) 36 | setupNavigationManager() 37 | } 38 | 39 | override fun onBackPressed() { 40 | if (bottomNavManager?.onBackPressed() == false) super.onBackPressed() 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/BottomNavHistory.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui 2 | 3 | import android.os.Parcelable 4 | import kotlinx.android.parcel.Parcelize 5 | 6 | /** 7 | * Created by Vipul Asri on 15/07/20. 8 | * BottomNavHistory maintains history for user's navigation of bottom tabs, it stores latest unique history 9 | * User action: Tab 1 -> Tab 2 -> Tab 3 -> Tab 2 10 | * Result: Tab 1 -> Tab 3 -> Tab 2 11 | */ 12 | 13 | @Parcelize 14 | data class BottomNavHistory( 15 | private val backStack: ArrayList = arrayListOf() 16 | ) : Parcelable { 17 | 18 | val size: Int 19 | get() = backStack.size 20 | 21 | val isEmpty: Boolean 22 | get() = backStack.isEmpty() 23 | 24 | val isNotEmpty: Boolean 25 | get() = backStack.isNotEmpty() 26 | 27 | fun push(entry: Int) { 28 | if (backStack.contains(entry)) { 29 | backStack.run { 30 | remove(entry) 31 | add(entry) 32 | } 33 | } else backStack.add(entry) 34 | } 35 | 36 | fun pop(exit: Int) { 37 | backStack.remove(exit) 38 | } 39 | 40 | fun current() = backStack.last() 41 | 42 | fun clear() { 43 | backStack.clear() 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/BottomNavManager.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui 2 | 3 | import android.os.Bundle 4 | import android.util.SparseArray 5 | import androidx.core.util.forEach 6 | import androidx.core.util.set 7 | import androidx.core.view.forEach 8 | import androidx.fragment.app.FragmentManager 9 | import androidx.lifecycle.Lifecycle 10 | import androidx.navigation.NavController 11 | import androidx.navigation.fragment.NavHostFragment 12 | import com.google.android.material.bottomnavigation.BottomNavigationView 13 | import com.vipulasri.multiplebackstacknavigation.R 14 | 15 | /** 16 | * Created by Vipul Asri on 15/07/20. 17 | * Ported from: https://github.com/android/architecture-components-samples/blob/master/NavigationAdvancedSample 18 | * Manages the various graphs needed for a [BottomNavigationView]. 19 | * 20 | * This sample is a workaround until the Navigation Component supports multiple back stacks. 21 | */ 22 | 23 | class BottomNavManager( 24 | private val fragmentManager: FragmentManager, 25 | private val containerId: Int, 26 | private val bottomNavigationView: BottomNavigationView 27 | ) { 28 | 29 | companion object { 30 | private const val KEY_NAV_HISTORY = "nav_history" 31 | } 32 | 33 | // Graph Id's of the tabs 34 | private val navGraphIds = 35 | listOf(R.navigation.nav_home, R.navigation.nav_dashboard, R.navigation.nav_notifications) 36 | 37 | // Map of tags 38 | private val graphIdToTagMap = SparseArray() 39 | 40 | // holds the start destination of all the graphs with their bottomNavigationView item id, used for back press 41 | private var navGraphStartDestinations = mutableMapOf() 42 | 43 | private var navHistory = BottomNavHistory().apply { push(R.id.navigation_home) } 44 | 45 | private var selectedNavController: NavController? = null 46 | 47 | fun onBottomNavChanged(listener: NavController.OnDestinationChangedListener) { 48 | selectedNavController?.addOnDestinationChangedListener(listener) 49 | } 50 | 51 | init { 52 | setupNavController() 53 | } 54 | 55 | fun setupNavController() { 56 | navGraphStartDestinations.clear() 57 | graphIdToTagMap.clear() 58 | 59 | // create a NavHostFragment for each NavGraph ID 60 | createNavHostFragmentsForGraphs() 61 | 62 | // When a navigation item is selected 63 | bottomNavigationView.setupItemClickListener() 64 | } 65 | 66 | private fun createNavHostFragmentsForGraphs() { 67 | // create a NavHostFragment for each NavGraph ID 68 | navGraphIds.forEachIndexed { index, navGraphId -> 69 | val fragmentTag = getFragmentTag(index) 70 | 71 | // Find or create the Navigation host fragment 72 | val navHostFragment = obtainNavHostFragment( 73 | fragmentTag, 74 | navGraphId 75 | ) 76 | 77 | // Obtain its id 78 | val graphId = navHostFragment.navController.graph.id 79 | navGraphStartDestinations[graphId] = navHostFragment.navController.graph.startDestination 80 | 81 | // Save to the map 82 | graphIdToTagMap[graphId] = fragmentTag 83 | 84 | // Attach or detach nav host fragment depending on whether it's the selected item. 85 | if (bottomNavigationView.selectedItemId == graphId) { 86 | // Update nav controller with the selected graph 87 | selectedNavController = navHostFragment.navController 88 | showNavHostFragment(navHostFragment, true) 89 | } else { 90 | showNavHostFragment(navHostFragment, false) 91 | } 92 | } 93 | } 94 | 95 | private fun BottomNavigationView.setupItemClickListener() { 96 | menu.forEach { item -> 97 | item.setOnMenuItemClickListener { 98 | 99 | // do nothing on tab re-selection 100 | if (item.isChecked) { 101 | return@setOnMenuItemClickListener true 102 | } 103 | 104 | if (!fragmentManager.isStateSaved) { 105 | item.isChecked = true 106 | navHistory.push(item.itemId) 107 | 108 | val newlySelectedItemTag = graphIdToTagMap[item.itemId] 109 | val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag) 110 | as NavHostFragment 111 | 112 | fragmentManager.beginTransaction() 113 | .show(selectedFragment) 114 | .setMaxLifecycle(selectedFragment, Lifecycle.State.RESUMED) 115 | .setPrimaryNavigationFragment(selectedFragment) 116 | .apply { 117 | // Detach all other Fragments 118 | graphIdToTagMap.forEach { _, fragmentTag -> 119 | if (fragmentTag != newlySelectedItemTag) { 120 | val fragment = fragmentManager.findFragmentByTag(fragmentTag)!! 121 | hide(fragment) 122 | setMaxLifecycle(fragment, Lifecycle.State.STARTED) 123 | } 124 | } 125 | } 126 | .commit() 127 | 128 | selectedNavController = selectedFragment.navController 129 | } 130 | 131 | true 132 | } 133 | } 134 | } 135 | 136 | // select particular bottom navigation item 137 | fun selectItem(itemId: Int) { 138 | bottomNavigationView.menu.findItem(itemId) 139 | ?.let { 140 | bottomNavigationView.menu.performIdentifierAction(itemId, 0) 141 | } 142 | } 143 | 144 | // controls the back press mechanism 145 | fun onBackPressed(): Boolean { 146 | return if (navHistory.isNotEmpty) { 147 | selectedNavController?.let { 148 | if (it.currentDestination == null || it.currentDestination?.id == navGraphStartDestinations[bottomNavigationView.selectedItemId]) { 149 | if (isFirstTab()) return false 150 | 151 | navHistory.pop(bottomNavigationView.selectedItemId) 152 | selectItem(navHistory.current()) 153 | return true 154 | } 155 | return false // super.onBackPressed() will be called, which will pop the fragment itself 156 | } ?: false 157 | } else false 158 | } 159 | 160 | // to save the tab history during any configuration change 161 | fun onSaveInstanceState(outState: Bundle?) { 162 | outState?.putParcelable(KEY_NAV_HISTORY, navHistory) 163 | } 164 | 165 | // to restore the tab history after any configuration change 166 | fun onRestoreInstanceState(savedInstanceState: Bundle?) { 167 | savedInstanceState?.let { 168 | navHistory = it.getParcelable(KEY_NAV_HISTORY) as BottomNavHistory 169 | } 170 | } 171 | 172 | // gets the NavHostFragment for particular index 173 | fun getNavHostFragment(index: Int): NavHostFragment? { 174 | return fragmentManager.findFragmentByTag(getFragmentTag(index)) as NavHostFragment? 175 | } 176 | 177 | private fun isFirstTab(): Boolean { 178 | return bottomNavigationView.selectedItemId == R.id.navigation_home 179 | } 180 | 181 | private fun showNavHostFragment( 182 | navHostFragment: NavHostFragment, 183 | show: Boolean 184 | ) { 185 | fragmentManager.beginTransaction() 186 | .apply { 187 | if (show) { 188 | show(navHostFragment) 189 | setMaxLifecycle(navHostFragment, Lifecycle.State.RESUMED) 190 | } else { 191 | hide(navHostFragment) 192 | setMaxLifecycle(navHostFragment, Lifecycle.State.STARTED) 193 | } 194 | } 195 | .commitNow() 196 | } 197 | 198 | private fun obtainNavHostFragment( 199 | fragmentTag: String, 200 | navGraphId: Int 201 | ): NavHostFragment { 202 | // If the Nav Host fragment exists, return it 203 | val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment? 204 | existingFragment?.let { return it } 205 | 206 | // Otherwise, create it and return it. 207 | val navHostFragment = NavHostFragment.create(navGraphId) 208 | fragmentManager.beginTransaction() 209 | .add(containerId, navHostFragment, fragmentTag) 210 | .commitNow() 211 | return navHostFragment 212 | } 213 | 214 | private fun FragmentManager.isOnBackStack(backStackName: String): Boolean { 215 | val backStackCount = backStackEntryCount 216 | for (index in 0 until backStackCount) { 217 | if (getBackStackEntryAt(index).name == backStackName) { 218 | return true 219 | } 220 | } 221 | return false 222 | } 223 | 224 | private fun getFragmentTag(index: Int) = "BottomNavManager#$index" 225 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/dashboard/DashboardFragment.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.dashboard 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Button 8 | import android.widget.TextView 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import androidx.lifecycle.Observer 12 | import androidx.lifecycle.ViewModelProviders 13 | import androidx.navigation.fragment.findNavController 14 | import com.vipulasri.multiplebackstacknavigation.R 15 | 16 | class DashboardFragment : Fragment() { 17 | 18 | private val dashboardViewModel: DashboardViewModel by viewModels() 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | return inflater.inflate(R.layout.fragment_dashboard, container, false) 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | 31 | val textView: TextView = view.findViewById(R.id.text_dashboard) 32 | val button: Button = view.findViewById(R.id.button) 33 | 34 | button.setOnClickListener { 35 | findNavController().navigate(R.id.action_first_screen) 36 | } 37 | 38 | dashboardViewModel.text.observe(viewLifecycleOwner, Observer { 39 | textView.text = it 40 | }) 41 | 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/dashboard/DashboardViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.dashboard 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | 7 | class DashboardViewModel : ViewModel() { 8 | 9 | private val _text = MutableLiveData().apply { 10 | value = "This is dashboard Fragment" 11 | } 12 | val text: LiveData = _text 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.home 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Button 8 | import android.widget.TextView 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import androidx.lifecycle.Observer 12 | import androidx.lifecycle.ViewModelProviders 13 | import androidx.navigation.fragment.findNavController 14 | import com.vipulasri.multiplebackstacknavigation.R 15 | 16 | class HomeFragment : Fragment() { 17 | 18 | private val homeViewModel: HomeViewModel by viewModels() 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | return inflater.inflate(R.layout.fragment_home, container, false) 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | 31 | val textView: TextView = view.findViewById(R.id.text_home) 32 | val button: Button = view.findViewById(R.id.button) 33 | 34 | button.setOnClickListener { 35 | findNavController().navigate(R.id.action_first_screen) 36 | } 37 | 38 | homeViewModel.text.observe(viewLifecycleOwner, Observer { 39 | textView.text = it 40 | }) 41 | 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.home 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | 7 | class HomeViewModel : ViewModel() { 8 | 9 | private val _text = MutableLiveData().apply { 10 | value = "This is home Fragment" 11 | } 12 | val text: LiveData = _text 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/notifications/NotificationsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.notifications 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Button 8 | import android.widget.TextView 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import androidx.lifecycle.Observer 12 | import androidx.lifecycle.ViewModelProviders 13 | import androidx.navigation.fragment.findNavController 14 | import com.vipulasri.multiplebackstacknavigation.R 15 | 16 | class NotificationsFragment : Fragment() { 17 | 18 | private val notificationsViewModel: NotificationsViewModel by viewModels() 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | return inflater.inflate(R.layout.fragment_notifications, container, false) 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | 31 | val textView: TextView = view.findViewById(R.id.text_notifications) 32 | val button: Button = view.findViewById(R.id.button) 33 | 34 | button.setOnClickListener { 35 | findNavController().navigate(R.id.action_first_screen) 36 | } 37 | 38 | notificationsViewModel.text.observe(viewLifecycleOwner, Observer { 39 | textView.text = it 40 | }) 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/notifications/NotificationsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.notifications 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | 7 | class NotificationsViewModel : ViewModel() { 8 | 9 | private val _text = MutableLiveData().apply { 10 | value = "This is notifications Fragment" 11 | } 12 | val text: LiveData = _text 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vipulasri/multiplebackstacknavigation/ui/title/TitleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.vipulasri.multiplebackstacknavigation.ui.title 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.fragment.app.Fragment 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.ViewModelProviders 11 | import androidx.navigation.fragment.navArgs 12 | import com.vipulasri.multiplebackstacknavigation.R 13 | 14 | class TitleFragment : Fragment() { 15 | 16 | private val args: TitleFragmentArgs by navArgs() 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, 20 | container: ViewGroup?, 21 | savedInstanceState: Bundle? 22 | ): View? { 23 | return inflater.inflate(R.layout.fragment_title, container, false) 24 | } 25 | 26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 27 | super.onViewCreated(view, savedInstanceState) 28 | 29 | val textView = view.findViewById(R.id.text_title) 30 | 31 | textView.text = args.title 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 25 | 26 |