I have a StorageAvctivity with 2 fragments inside.
class StorageActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_storage)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
}
This activity has a navigation graph
<?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/storage_nav_graph"
app:startDestination="#id/downloadFragment">
<fragment
android:id="#+id/downloadFragment"
android:name=".ui.fragments.storage.DownloadFragment"
android:label="fragment_download"
tools:layout="#layout/fragment_download" >
<action
android:id="#+id/action_downloadFragment_to_largeDownloadFragment"
app:destination="#id/largeDownloadFragment" />
</fragment>
<fragment
android:id="#+id/largeDownloadFragment"
android:name=".ui.fragments.storage.LargeDownloadFragment"
android:label="fragment_large_download"
tools:layout="#layout/fragment_large_download" />
</navigation>
So when the StorageActivity opens, the DownloadFragment appears. If i press the back button storageActivity finishes so i go back to MainActivity. However, from DownloadFragment i can go to LargeDownloadFragment. So in that case if i press the back arrow button still the StorageActivity finishes and gets me back to MainActivity. What i want is to navigate me back to DownloadFragment. How can i do that?
Related
I'm trying to update my app to use BottomNavigationView. The first tab contains a HostFragment with a loading spinner that performs a network request to determine which fragment, either HomeFragment or LockedFragment, should be shown in that tab.
MainActivity handles the initial setup of the BottomNavigationView:
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager.findFragmentById(
R.id.nav_host_container
) as NavHostFragment
navController = navHostFragment.navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.mainFragment)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}
My main nav graph looks like this:
<?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"
android:id="#+id/nav_graph"
app:startDestination="#+id/home">
<include app:graph="#navigation/home"/>
<include app:graph="#navigation/list"/>
<include app:graph="#navigation/form"/>
</navigation>
with the home graph looking like:
<?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"
android:id="#+id/home"
app:startDestination="#+id/hostFragment">
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment" />
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.android.bottomnav.homescreen.HomeFragment"
android:label="Home" />
<fragment
android:id="#+id/lockedFragment"
android:name="com.example.android.bottomnav.homescreen.LockedFragment"
android:label="Locked"/>
</navigation>
HostFragment get's shown fine and loads it's data:
class HostFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_host, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
determineFragmentToShow()
}
private fun determineFragmentToShow() {
lifecycleScope.launchWhenStarted {
// mock network call to determine tab
delay(1500)
// show HomeFragment for the sake of the example, but note that
// this would be dependent on the network call's result above
findNavController().navigate(R.id.action_hostFragment_to_homeFragment)
}
}
}
which successfully navigates us to HomeFragment.
Now the problem is that whenever I press the back button from HomeFragment it goes back to HostFragment instead of closing the app.You can see the behavior in this video here.
I tried to set the popUpTo and popUpInclusive tags inside of home.xml like this:
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
</fragment>
That got the app to close when pressing back from HomeFragment, but now each time I switch to a new tab, it creates a new instance of the fragment and adds it to the backstack. Pressing the back button then will traverse them all backwards. You can see that behavior in this video here.
So how can I update the start destination of a nested navigation graph?
I'm using the latest 2.4.0-alpha10 of navigation component so that I can get native support for multiple backstacks. Any help is greatly appreciated!
I was able to utilize the answer here to get a solution working for me.
Inside of determineFragmentToShow() in HostFragment, I just replaced findNavController().navigate(R.id.action_hostFragment_to_homeFragment) with
val navController = findNavController()
val graph = navController.graph
val walletGraph = graph.findNode(R.id.home) as NavGraph
walletGraph.setStartDestination(R.id.homeFragment)
navController.navigate(R.id.action_hostFragment_to_homeFragment)
I still needed to include the popUpTo and popUpInclusive tags here
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
</fragment>
but this got me the back behavior I was looking for!
My app right now has three screens: Home, Rockets and Company. When I open the app, it's on Home screen and there's a hamburger icon that opens the Navigation drawer. The problem is that when I select Rockets or Company from the drawer, the hamburger icon is replaced with a back arrow. Clicking on the arrow will navigate back to the Home screen. The hamburger icon reappears when I'm on Home screen.
activity_main.xml:
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar_main"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/midnight_blue" />
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header"
app:menu="#menu/nav_menu"
/>
</androidx.drawerlayout.widget.DrawerLayout>
nav_menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/homeFragment"
android:icon="#drawable/ic_home"
android:title="#string/nav_menu_home"
/>
<group
android:id="#+id/group_data"
>
<item
android:id="#+id/rocketsDataFragment"
android:icon="#drawable/ic_rocket"
android:title="#string/nav_menu_rockets_data"
/>
<item
android:id="#+id/companyDataFragment"
android:icon="#drawable/ic_company"
android:title="#string/nav_menu_company_data"
/>
</group>
<group
android:id="#+id/group_launches"
>
<item
android:id="#+id/past_launches"
android:icon="#drawable/ic_prev"
android:title="#string/nav_menu_launches_past"
/>
<item
android:id="#+id/upcoming_launches"
android:icon="#drawable/ic_next"
android:title="#string/nav_menu_launches_upcoming"
/>
</group>
</menu>
nav_graph.xml
<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/nav_graph"
app:startDestination="#id/homeFragment"
>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.rocketman.home.HomeFragment"
android:label="#string/nav_menu_home"
tools:layout="#layout/fragment_home"
/>
<fragment
android:id="#+id/rocketsDataFragment"
android:name="com.example.rocketman.rocket.RocketsDataFragment"
android:label="#string/nav_menu_rockets_data"
tools:layout="#layout/fragment_rockets_data"
/>
<fragment
android:id="#+id/companyDataFragment"
android:name="com.example.rocketman.company.CompanyDataFragment"
android:label="#string/nav_menu_company_data"
tools:layout="#layout/fragment_company_data"
/>
</navigation>
And finally, MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val navController by lazy {
(supportFragmentManager
.findFragmentById(R.id.fragment_nav_host) as NavHostFragment).navController
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbarMain)
setupNavigationDrawer()
}
private fun setupNavigationDrawer() {
navController.let { navController ->
NavigationUI.setupActionBarWithNavController(
this,
navController,
binding.drawerLayout
)
NavigationUI.setupWithNavController(binding.navView, navController)
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, binding.drawerLayout)
}
override fun onBackPressed() {
if(binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
}
So how can I make my screens have access to the Navigation Drawer instead of being navigation paths of Home screen?
In other words, I want my screen to all be able to go to another screen through Navigation drawer instead of going from that screen to Home screen and then go to another screen.
It seems like you didn't use AppBarConfiguration in your code which will resolve your problem.
AppBarConfiguration:
NavigationUI uses an AppBarConfiguration object to manage the behavior of the Navigation button in the upper-left corner of your app's display area. The Navigation button’s behavior changes depending on whether the user is at a top-level destination.
See the doc's for more understanding Update UI components with NavigationUI
I have updated the code of your Activity below. If Navigation Extensions Functions show's error makes sure to have these dependencies in your build.gradle file.
Dependencies:
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
Updated Activity Code:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var appBarConfiguration: AppBarConfiguration
private val navController by lazy {
(supportFragmentManager
.findFragmentById(R.id.fragment_nav_host) as NavHostFragment).navController
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbarMain)
setupNavigationDrawer()
}
private fun setupNavigationDrawer() {
navController.let { navController ->
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(setOf(
R.id.homeFragment, R.id.rocketsDataFragment, R.id.companyDataFragment), binding.drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.navView.setupWithNavController(navController)
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onBackPressed() {
if(binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
}
NOTE: If you're using Android Studio version 4.1.2 by creating a new project (Navigation Drawer Activity Template) it will setup the project with AppBarConfiguration with all needed dependencies. So you can also try that.
In MainActivity I have next method:
override fun onBackPressed() {
val onBackPressListener = currentFragmentOnTop() as? OnBackPressListener
if (onBackPressListener?.onBackPress() != true) super.onBackPressed()
}
But sometimes he doesn't work correctly.
For example:
In my startDestination fragment I call dialog(bottomMenuDialog) with menuItems, by clicking on which I call fragments:
findNavController().navigate(R.id.settings_list)
Here is code of my nav_graph.xml:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph"
app:startDestination="#id/authorizationsListFragment">
<fragment
android:id="#+id/authorizationsListFragment"
android:name="com.mandarine.sai.features.authorizations.list.AuthorizationsListFragment"
android:label="AuthorizationsListFragment">
<action
android:id="#+id/settings_list"
app:destination="#+id/settingsListFragment" />
<action
android:id="#+id/connections_list"
app:destination="#+id/connectionsListFragment" />
<action
android:id="#+id/bottom_menu_dialog"
app:destination="#+id/bottomMenuDialog" />
</fragment>
But I can't click on the back button to go to the start fragment.
Behavior: it opens the start fragment and immediately goes back to the current one, to settings_list.
override fun onBackPressed() {
finish()
}
I'm using the Actionbar and I have an icon in the appbar which when clicked should go to CategoryFragment. But I want to make it so that if that fragment is already on the backstack, it should either remove it and then add a new instance of CategoryFragment to the backstack or replace it.
I tried the code below, but it does not work and only overlays/superimposes another copy of CategoryFragment on top of the existing CategoryFragment and also produces very odd and unexpected behaviors. I'm not using the navcontroller to navigate to CategoryFragment since it adds another copy of the fragment in the backstack. How can I make this work?
onOptionsItemSelected() in Main Activity:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
Log.i("Lifecycle-Activity", "OnOptionsItemSelected() called")
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
R.id.categoryFragment -> {
var backStateName = CategoryFragment::class.simpleName
var fragmentPopped = supportFragmentManager.popBackStackImmediate(backStateName, 0)
if(!fragmentPopped)
{
var ft = supportFragmentManager.beginTransaction()
//also tried R.id.main_activity_container which is not working
ft.replace(R.id.navHostFragment, CategoryFragment())
ft.addToBackStack(backStateName)
ft.commit()
}
return true
}
return true
}
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
Log.i("Lifecycle-Activity", "OnCreate() called")
navController = Navigation.findNavController(this, R.id.navHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
appBarConfiguration = AppBarConfiguration.Builder(navController.graph)
.build()
}
override fun onSupportNavigateUp(): Boolean {
super.onSupportNavigateUp()
return NavigationUI.navigateUp(navController, appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
Log.i("Lifecycle-Activity", "OnCreateOptionsMenu() called")
menuInflater.inflate(R.menu.main_menu, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
Log.i("Lifecycle-Activity", "OnOptionsItemSelected() called")
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
R.id.categoryFragment -> {
var backStateName = CategoryFragment::class.simpleName
var fragmentPopped = supportFragmentManager.popBackStackImmediate(backStateName, 0)
if(!fragmentPopped)
{
var ft = supportFragmentManager.beginTransaction()
//also tried R.id.main_activity_container which is not working
ft.replace(R.id.navHostFragment, CategoryFragment())
ft.addToBackStack(backStateName)
ft.commit()
}
}
return true
}
return true
}
Navgraph:
<?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/nav_graph_main"
app:startDestination="#id/categoriesFragment">
<fragment
android:id="#+id/clockFragment"
android:name="com.example.pomoplay.ui.main.ClockFragment"
android:label="Pomo Clock"
tools:layout="#layout/fragment_clock" />
<fragment
android:id="#+id/categoryFragment"
android:name="com.example.pomoplay.ui.main.CategoryFragment"
android:label="Category"
tools:layout="#layout/fragment_category">
<action
android:id="#+id/action_open_category_fragment"
app:destination="#id/categoryFragment"
app:popUpTo="#id/categoryFragment"
app:popUpToInclusive="true" />
<action
android:id="#+id/action_categoryFragment_to_clockFragment"
app:destination="#id/clockFragment" />
<action
android:id="#+id/action_categoryFragment_to_newTaskDialogFragment"
app:destination="#id/newTaskDialogFragment" />
<argument
android:name="category"
app:argType="com.example.pomoplay.Category" />
</fragment>
<fragment
android:id="#+id/categoriesFragment"
android:name="com.example.pomoplay.ui.main.CategoriesFragment"
android:label="Categories"
tools:layout="#layout/fragment_categories">
<action
android:id="#+id/action_categoriesFragment_to_newCategoryDialogFragment"
app:destination="#id/newCategoryDialogFragment" />
<argument
android:name="category"
app:argType="com.example.pomoplay.Category" />
<action
android:id="#+id/action_categoriesFragment_to_categoryFragment"
app:destination="#id/categoryFragment" />
<argument
android:name="fromNewCategoryDialog"
app:argType="boolean"
android:defaultValue="false" />
</fragment>
<dialog
android:id="#+id/newCategoryDialogFragment"
android:name="com.example.pomoplay.ui.main.NewCategoryDialogFragment"
tools:layout="#layout/fragment_new_category_dialog">
<action
android:id="#+id/action_newCategoryDialogFragment_to_categoriesFragment"
app:destination="#id/categoriesFragment"
app:popUpToInclusive="false" />
</dialog>
<dialog
android:id="#+id/newTaskDialogFragment"
android:name="com.example.pomoplay.ui.main.NewTaskDialogFragment"
android:label="fragment_new_task_dialog"
tools:layout="#layout/fragment_new_task_dialog" >
<action
android:id="#+id/action_newTaskDialogFragment_to_categoryFragment"
app:destination="#id/categoryFragment"
app:popUpTo="#+id/categoryFragment"
app:popUpToInclusive="true" />
</dialog>
</navigation>
You're using NavController and therefore you should not be doing any manual FragmentTransactions at all.
Instead, you should Navigation to a destination, in this case, by using Navigation's support for popUpTo to pop your CategoryFragment off the stack.
Assuming you have a destination set up in your navigation XML for your CategoryFragment with code such as:
<fragment
android:id="#+id/categoryFragment"
android:name=".CategoryFragment/>
You can create an action that includes the popUpTo flag you need (you'd put this directly below the <fragment> itself:
<action
android:id+"#+id/open_category"
app:popUpTo="#id/categoryFragment"
app:popUpToInclusive="true"
app:destination="#id/categoryFragment"/>
This says:
Create an action named open_category
When you trigger this action, pop the stack back up to categoryFragment if it already exists on the back stack (otherwise, this does nothing). The popUpToInclusive means that the previous instance is popped.
After you pop the previous instance, create a new instance of categoryFragment and add it to your back stack.
Then you can trigger that action from your onOptionsItemSelected()':
override fun onOptionsItemSelected(item: MenuItem): Boolean {
Log.i("Lifecycle-Activity", "OnOptionsItemSelected() called")
return when (item.itemId) {
R.id.categoryFragment -> {
// Trigger the action
navController.navigate(R.id.open_category);
true
}
default -> {
// android.R.id.home is handled by the call to super,
// you do not need to handle it here.
super.onOptionsItemSelected(item)
}
}
}
I used to have a single activity that included the navigation host and the BottomNavigationView that allowed switching views between the 3 fragments. I recently changed my design so that the initial activity now represents a single new fragment, and there exists a button that allows the user to navigate to another fragment which should hold the aforementioned 3 tabbed bottom navigation.
I've managed to implement the initial activity and the first fragment. But the issue that I am having is that when I navigate to the second fragment with the 3 tabbed bottom navigation bar, I don't know how to implement the onClickListener on the tabs. It used to be fairly straightforward when I had the bottom navigation inside the AppCompatActivity. I'm guessing that fragments are not supposed to be used for this case. I could easily use another activity class but I wanted to build a single activity application and wondered if there was another solution to this.
In the original method using an activity class, I was able to implement the bottom navigation like below:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
setupBottomNavMenu(navController)
setupActionBar(navController)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_toolbar, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
val navigated = NavigationUI.onNavDestinationSelected(item!!, navController)
return navigated || super.onOptionsItemSelected(item)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.nav_host_fragment), drawer_layout)
}
private fun setupBottomNavMenu(navController: NavController) {
bottom_nav?.let {
NavigationUI.setupWithNavController(it, navController)
}
}
private fun setupActionBar(navController: NavController) {
NavigationUI.setupActionBarWithNavController(this, navController, drawer_layout)
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#color/colorMintCream"
tools:context=".activity.MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:theme="#style/ToolbarTheme"/>
<fragment
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/main_nav_graph"
app:defaultNavHost="true"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="#menu/menu_navigation"/>
</LinearLayout>
</layout>
The navigation looks like follows:
<?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" android:id="#+id/mobile_navigation.xml"
app:startDestination="#id/destination_profile">
<fragment
android:id="#+id/destination_profile"
android:name="com.example.project.profile.ProfileFragment"
android:label="Profile" />
<fragment
android:id="#+id/destination_achievements"
android:name="com.example.project.fragments.AchievementsFragment"
android:label="Achievements" />
<fragment
android:id="#+id/destination_logs"
android:name="com.example.project.fragments.LogsFragment"
android:label="Logs" />
<fragment
android:id="#+id/destination_settings"
android:name="com.example.project.fragments.SettingsFragment"
android:label="Settings" />
</navigation>
And finally the menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/destination_profile"
android:icon="#drawable/ic_person_black_24dp"
android:title="#string/profile"/>
<item
android:id="#+id/destination_logs"
android:icon="#drawable/ic_fitness_center_black_24dp"
android:title="#string/logs"/>
<item
android:id="#+id/destination_achievements"
android:icon="#drawable/ic_star_black_24dp"
android:title="#string/achievements"/>
</menu>
Similarly I tried to apply this on a fragment like below:
class MainFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
var binding: FragmentMainBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_main,
container,
false
)
val application = requireNotNull(this.activity).application
val dataSource = UserProfileDatabase.getInstance(application).userProfileDao
val viewModelFactory = MainViewModelFactory(dataSource, application)
val navController = this.findNavController()
NavigationUI.setupWithNavController(binding.bottomNav, navController)
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_toolbar, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController = this.findNavController()
val navigated = NavigationUI.onNavDestinationSelected(item, navController)
return navigated || super.onOptionsItemSelected(item)
}
companion object {
fun newInstance(): MainFragment = MainFragment()
}
}
The xml is almost exactly the same as the one for activity.
I expected to invoke onOptionsItemSelected() when I clicked on the tab elements, but I wasn't able to do so. How can I implement the listeners on those tab elements so that I can navigate to the correct fragment destinations?
Why don't you keep your first implementation for your BottomNavigationView which is good and combine it with your new implementation by using addOnDestinationChangedListener.
Your FirstFragment that hold the button will hide the BottomNavigationView and the SecondFragment with the 3 tabbed bottom navigation will show it.
For example :
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.id == R.id.first_fragment) {
// your intro fragment will hide your bottomNavigationView
bottomNavigationView.visibility = View.GONE
} else if (destination.id == R.id.second_fragment){
// your second fragment will show your bottomNavigationView
bottomNavigationView.visibility = View.VISIBLE
}
}
in that way you keep the control in your Activity instead of Fragment and by that interact better with your listeners and your NavController.
Keep your menu item's id same as your graph id and simply use 1 line code i.e
val navHostFragment =childFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
val navController = navHostFragment.navController
binding.bottomNavBar.setupWithNavController(navController)