Using Navigation Architecture Component with Navigation Drawer - android

I am trying to use the Navigation Architecture Component (NavHostFragment) with a Navigation Drawer (widget.NavigationView). I get one of the following two errors.
1) This can happen when selecting an item from the drawer several times:
java.lang.IllegalArgumentException: navigation destination app.myDomain.navdrawertrials:id/action_rootFragment_to_settingsFragment is unknown to this NavController
2) This happens from my real code base that is set up the same way as the simplified sample below AFAICT. Why would a current navigation node not be set?
java.lang.IllegalStateException: no current navigation node
Simplified Code
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
setupToolbar()
setupNavDrawer()
setupNavigation()
}
private fun setupToolbar() {
setSupportActionBar( toolbar )
}
private fun setupNavigation() {
val navController = findNavController( R.id.nav_host_fragment)
setupActionBarWithNavController( navController, main_activity_drawer_layout )
}
private fun setupNavDrawer() {
val toggle = ActionBarDrawerToggle(
this,
main_activity_drawer_layout,
toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close)
main_activity_drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_drawer.setNavigationItemSelectedListener {
val navController = findNavController( R.id.nav_host_fragment )
when (it.itemId) {
R.id.nav_drawer_root_menu_item -> navController.navigate(R.id.rootFragment)
R.id.nav_drawer_first_menu_item -> navController.navigate(R.id.action_rootFragment_to_firstFragment)
R.id.nav_drawer_settings_menu_item -> navController.navigate(R.id.action_rootFragment_to_settingsFragment)
}
main_activity_drawer_layout.closeDrawer(GravityCompat.START)
true
}
}
override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()
}
main_activity.xml
<android.support.v4.widget.DrawerLayout
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/main_activity_drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/main_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="#+id/nav_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_drawer_header"
app:menu="#menu/nav_drawer_menu" />
</android.support.v4.widget.DrawerLayout>
nav_drawer_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/nav_drawer_root_menu_item"
android:title="To Root" />
<item
android:id="#+id/nav_drawer_first_menu_item"
android:title="To First" />
<item
android:id="#+id/nav_drawer_settings_menu_item"
android:title="To Settings" />
</menu>
main_activity_content.xml
<android.support.design.widget.CoordinatorLayout
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"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
</android.support.design.widget.AppBarLayout>
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="#navigation/nav_graph"
app:defaultNavHost="true"
/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
app:srcCompat="#android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
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/rootFragment">
<fragment
android:id="#+id/rootFragment"
android:name="app.anytune.navdrawertrials.RootFragment"
android:label="root_fragment"
tools:layout="#layout/root_fragment" >
<action
android:id="#+id/action_rootFragment_to_firstFragment"
app:destination="#id/firstFragment" />
<action
android:id="#+id/action_rootFragment_to_settingsFragment"
app:destination="#id/settingsFragment" />
</fragment>
<fragment
android:id="#+id/firstFragment"
android:name="app.anytune.navdrawertrials.FirstFragment"
android:label="first_fragment"
tools:layout="#layout/first_fragment" />
<fragment
android:id="#+id/settingsFragment"
android:name="app.anytune.navdrawertrials.SettingsFragment"
android:label="settings_fragment"
tools:layout="#layout/settings_fragment" />
</navigation>
root_fragment.xml (other nodes are similar empty fragments with just a label)
<FrameLayout 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=".RootFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Root Fragment" />
</FrameLayout>

Regarding the first error, based on your code, when user select 'first' or 'settings' from drawer he is transferred to 'first' or 'settings' fragment using action_rootFragment_to_firstFragment or action_rootFragment_to_settingsFragment action, but if you try to select again 'first' or 'settings' from drawer there is no action_rootFragment_to_firstFragment or action_rootFragment_to_settingsFragment action inside firstFragment or settingsFragment element inside navigation graph.
The solution is to change:
when (it.itemId) {
R.id.nav_drawer_root_menu_item -> navController.navigate(R.id.rootFragment)
R.id.nav_drawer_first_menu_item -> navController.navigate(R.id.action_rootFragment_to_firstFragment)
R.id.nav_drawer_settings_menu_item -> navController.navigate(R.id.action_rootFragment_to_settingsFragment)
}
to:
when (it.itemId) {
R.id.nav_drawer_root_menu_item -> navController.navigate(R.id.rootFragment)
R.id.nav_drawer_first_menu_item -> navController.navigate(R.id.firstFragment)
R.id.nav_drawer_settings_menu_item -> navController.navigate(R.id.settingsFragment)
}
The better solution is to tie destinations to menu-driven UI components(in your case drawer), change your menu items id to same as destinations id's, like this:
<item
android:id="#+id/rootFragment"
android:title="To Root" />
<item
android:id="#+id/firstFragment"
android:title="To First" />
<item
android:id="#+id/settingsFragment"
android:title="To Settings" />
and add
setupWithNavController(nav_view, navController )
inside your main activity, instead of
nav_drawer.setNavigationItemSelectedListener {
val navController = findNavController( R.id.nav_host_fragment )
when (it.itemId) {
R.id.nav_drawer_root_menu_item -> navController.navigate(R.id.rootFragment)
R.id.nav_drawer_first_menu_item -> navController.navigate(R.id.action_rootFragment_to_firstFragment)
R.id.nav_drawer_settings_menu_item -> navController.navigate(R.id.action_rootFragment_to_settingsFragment)
}
main_activity_drawer_layout.closeDrawer(GravityCompat.START)
true
}

2) If the crash occurs on orientation change. Use the below on the activity
upgrade navigation version module to 2.2.0 or above
implementation "androidx.navigation:navigation-fragment-ktx:2.2.0"
implementation "androidx.navigation:navigation-ui-ktx:2.2.0"

Related

BottomNavigationBar not working properly after navigate to navigation fragment by cutom button click

Take a Bottom Navigation activity from android studio template. there are 3 fragment with 3 item in BottomNavBar (HomeFragment, DashboardFragment, NotificationsFragment) navigate to DashboardFragment from HomeFragment by a button click. after that home item click from BottomNavBar should open Homefragment. But not working as expected.
Go to DashboardFrament from HomeFrament by
textView.setOnClickListener {
findNavController().navigate(R.id.navigation_dashboard)
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
navView.setupWithNavController(navController)
}
}
main_activity.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:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFramgent.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val textView: TextView = view.findViewById(R.id.text_home)
textView.setOnClickListener {
findNavController().navigate(R.id.navigation_dashboard)
}
}
navigation.xml
<?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/navigation_home">
<fragment
android:id="#+id/navigation_home"
android:name="com.app.bottomnav.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/navigation_dashboard"
android:name="com.app.bottomnav.ui.dashboard.DashboardFragment"
android:label="#string/title_dashboard"
tools:layout="#layout/fragment_dashboard" />
<fragment
android:id="#+id/navigation_notifications"
android:name="com.app.bottomnav.ui.notifications.NotificationsFragment"
android:label="#string/title_notifications"
tools:layout="#layout/fragment_notifications" />
</navigation>
Whenever you are navigating to any root destination from another fragment, you should have to clear the previous stack using popUpTo option builder.
Update your code navigation code in your home fragment.
findNavController()
.navigate(R.id.navigation_dashboard,
null,
NavOptions.Builder()
.setPopUpTo(R.id.navigation_home, true)
.build()
)
I don't know what is the best solution for that problem but here is a workaround:
In your activity you add public method:
fun navigateToNavBarDestination(destinationId: Int) {
binding.navView.setSelectedItemId(destinationId)
}
And in your fragments referenced in BottomNavigationView instead calling NavController::navigate you navigate in that way, lets say to dashboard:
fun navigateToDashboard() {
(activity as? MainActivity)?.navigateToNavBarDestination(R.id.dashboard)
}
Its not the perfect solution but works.
Add these lines in your MainActivity onCreate.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_dashboard,
R.id.navigation_notifications
))
setupActionBarWithNavController(navController, appBarConfiguration)

Problem with Android Navigation: Multiple back stacks

My problem is when I try to change the fragment with the bottom navigation it works except in one case. I have 5 fragments, when I enter in the 3rd and after go to the 5th, the icon of the 5th donĀ“t change it color. After that when I do another navigation the APP crash and give me the next error:
java.lang.IndexOutOfBoundsException: fromIndex = -1
at java.util.ArrayList.subListRangeCheck(ArrayList.java:1014)
at java.util.ArrayList.subList(ArrayList.java:1008)
at androidx.navigation.fragment.FragmentNavigator.popBackStack(FragmentNavigator.kt:80)
at androidx.navigation.NavController.popBackStackInternal(NavController.kt:275)
at androidx.navigation.NavController.popBackStackInternal(NavController.kt:558)
at androidx.navigation.NavController.navigate(NavController.kt:1682)
at androidx.navigation.NavController.navigate(NavController.kt:1541)
at androidx.navigation.NavController.navigate(NavController.kt:1468)
at androidx.navigation.ui.NavigationUI.onNavDestinationSelected(NavigationUI.kt:92)
at androidx.navigation.ui.NavigationUI.setupWithNavController$lambda-6(NavigationUI.kt:602)
at androidx.navigation.ui.NavigationUI.$r8$lambda$6wzEv9QqEZKdQFS1sQQy-bdQvgE(Unknown Source:0)
at androidx.navigation.ui.NavigationUI$$ExternalSyntheticLambda2.onNavigationItemSelected(Unknown Source:2)
at com.google.android.material.navigation.NavigationBarView$1.onMenuItemSelected(NavigationBarView.java:295)
at androidx.appcompat.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:834)
at androidx.appcompat.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:158)
at androidx.appcompat.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:985)
at com.google.android.material.navigation.NavigationBarMenuView$1.onClick(NavigationBarMenuView.java:133)
at android.view.View.performClick(View.java:7520)
at android.view.View.performClickInternal(View.java:7489)
at android.view.View.access$3600(View.java:826)
at android.view.View$PerformClick.run(View.java:28555)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:233)
at android.app.ActivityThread.main(ActivityThread.java:8010)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)
But if previously i clicked in the 5th fragment and go to the 3rd and after 5th again it works correctly.
For my code I follow this tutorial and the code are the same in both cases.
edit: Here is my code to add the navigation in MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if(savedInstanceState == null){
setupBottomNavigationBar()
}
private fun setupBottomNavigationBar(){
val graphs = setOf(
R.id.firstFragment,
R.id.secondFragment,
R.id.thirdFragment,
R.id.fourthFragment,
R.id.fithFragment
)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment
currentNavController = navHostFragment.navController
val bottomNavigation = findViewById<BottomNavigationView>(R.id.bottom_navigation)
bottomNavigation.setupWithNavController(currentNavController)
appBarConfiguration = AppBarConfiguration(graphs)
}
activity_main.xml
<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=".activities.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="#navigation/super_nav"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="#menu/bottom_nav"
app:itemIconTint="#drawable/botton_navigation_colors"
app:labelVisibilityMode="unlabeled"
app:itemIconSize="35dp"
/>
bottom_nav.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/first_navigation"
android:icon="#drawable/ic_first_black"
android:contentDescription="#string/first_desc"
android:title="#string/first_title" />
<item
android:id="#+id/second_navigation"
android:icon="#drawable/ic_second"
android:contentDescription="#string/second_desc"
android:title="#string/second_title" />
<item
android:id="#+id/third_navigation"
android:icon="#drawable/ic_third"
android:contentDescription="#string/third_desc"
android:title="#string/third_title" />
<item
android:id="#+id/fourth_navigation"
android:icon="#drawable/ic_fourth"
android:contentDescription="#string/fourth_desc"
android:title="#string/fourth_title" />
<item
android:id="#+id/fifth_navigation"
android:icon="#drawable/ic_fifth"
android:contentDescription="#string/fifth_desc"
android:title="#string/fifth_title" />
And super_nav.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/super_nav"
app:startDestination="#+id/main_navigation">
<include app:graph="#navigation/first_navigation"/>
<include app:graph="#navigation/second_navigation"/>
<include app:graph="#navigation/third_navigation" />
<include app:graph="#navigation/fourth_cart_navigation" />
<include app:graph="#navigation/fifth_navigation" />
I've just fixed very similar issue. The problem in my case was that I had destinations with the same id in several navigation subgraphs.
I mean:
<include app:graph="#navigation/first_sub_graph"/>
<include app:graph="#navigation/second_sub_graph"/>
<navigation
android:id="#+id/first_sub_graph"
..>
...
<fragment
android:id="#+id/**destinationID_1**"
...
/>
</navigation>
<navigation
android:id="#+id/second_sub_graph"
..>
...
<fragment
android:id="#+id/**destinationID_1**"
...
/>
</navigation>

Using navigation component with bottom navigation bar by using FragmentContainerView tag

I have already looked this question: FragmentContainerView using findNavController about this problem. But however I stiil couldn't solve the problem. It never opens other fragments. I thought maybe it is because of view binding but I tried to do it again without using view binding. It doesn't open still.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
private lateinit var navHostFragment: NavHostFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
binding.bottomNavigationView.setupWithNavController(navController)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
navHostFragment.navController.popBackStack()
return true
}
}
return super.onOptionsItemSelected(item)
}
}
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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/main_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/nav_graph_main" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="56dp"
app:menu="#menu/menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
nav_graph_main
<?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/fragmentLibrary">
<fragment
android:id="#+id/fragmentLibrary"
android:name="com.project.biosec.FragmentLibrary"
android:label="fragment_library"
tools:layout="#layout/fragment_library" />
<fragment
android:id="#+id/fragmentTransaction"
android:name="com.project.biosec.FragmentTransaction"
android:label="fragment_transaction"
tools:layout="#layout/fragment_transaction" />
<fragment
android:id="#+id/fragmentAccount"
android:name="com.project.biosec.FragmentAccount"
android:label="fragment_account"
tools:layout="#layout/fragment_account" >
<action
android:id="#+id/action_fragmentAccount_to_changeSignatureFragment"
app:destination="#id/changeSignatureFragment" />
<action
android:id="#+id/action_fragmentAccount_to_securityFragment"
app:destination="#id/securityFragment" />
<action
android:id="#+id/action_fragmentAccount_to_helpFragment"
app:destination="#id/helpFragment" />
<action
android:id="#+id/action_fragmentAccount_to_termsFragment"
app:destination="#id/termsFragment" />
</fragment>
<fragment
android:id="#+id/changeSignatureFragment"
android:name="com.project.biosec.ChangeSignatureFragment"
android:label="fragment_change_signature"
tools:layout="#layout/fragment_change_signature" />
<fragment
android:id="#+id/securityFragment"
android:name="com.project.biosec.SecurityFragment"
android:label="fragment_security"
tools:layout="#layout/fragment_security" />
<fragment
android:id="#+id/helpFragment"
android:name="com.project.biosec.HelpFragment"
android:label="fragment_help"
tools:layout="#layout/fragment_help" />
<fragment
android:id="#+id/termsFragment"
android:name="com.project.biosec.TermsFragment"
android:label="fragment_terms"
tools:layout="#layout/fragment_terms" />
</navigation>
menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/feed_fragment"
android:icon="#drawable/ic_library"
android:title="#string/library" />
<item
android:id="#+id/messages_fragment"
android:icon="#drawable/ic_assignment"
android:title="#string/transactions" />
<item
android:id="#+id/profile_fragment"
android:icon="#drawable/ic_account"
android:title="#string/account" />
</menu>
As per the Setting up bottom navigation guide:
Note: Setting up bottom navigation requires that you also set up your navigation graph and menu xml as described in Tie destinations to menu items.
That section specifically states that the android:id of your destination in your navigation graph XML file needs to match the android:id of the menu item in your menu XML file.
In your case, your navigation XML uses android:id="#+id/fragmentLibrary", android:id="#+id/fragmentTransaction", and android:id="#+id/fragmentAccount", so your menu items should change to use those same IDs.

Using Navigation's actions instead of direct fragment id crashes app

I'm having an issue with a very simple task with Navigation component.
I have just 2 screens: MainFragment and SearchFragment.
When I try to go from MainFragment to SearchFragment by navigation with an action it works perfectly. Then I press the back button and naturally it goes back to the MainFragment.
The issue is, when I click the same button the second time to go again to the SearchFragment, I receive the following error:
java.lang.IllegalArgumentException: Navigation action/destination action_mainFragment_to_searchFragment cannot be found from the current destination Destination(searchFragment)
I'm navigating from the MainFragment to Search like this:
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
I tried redoing the nav_graph.xml but without success.
If I just navigate with the id directly, it works fine and I go back and forth as many times as I want
findNavController().navigate(R.id.searchFragment)
Any ideas how to fix the issue with the safe args?
Edit:
This is my nav_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/nav_graph_main"
app:startDestination="#id/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="news.presentation.main.MainFragment"
android:label="fragment_main"
tools:layout="#layout/fragment_main">
<action
android:id="#+id/action_mainFragment_to_searchFragment"
app:destination="#id/searchFragment" />
</fragment>
<fragment
android:id="#+id/searchFragment"
android:name="news.presentation.search.SearchFragment"
android:label="fragment_search"
tools:layout="#layout/fragment_search" />
</navigation>
This is the activity (it's basically just a container for the fragments):
This is the activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView 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/homeFragContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph_main"
tools:context=".presentation.HomeActivity" />
And this is the HomeActivity.kt
#AndroidEntryPoint
class HomeActivity : AppCompatActivity(R.layout.activity_home)
This is the HomeFragment:
#AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
private val binding by viewBinding(FragmentMainBinding::bind)
private val viewModel by viewModels<MainViewModel>()
private val articlesAdapter = ArticlesAdapter(::onSubscriptionClicked)
private lateinit var layoutManager: LinearLayoutManager
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
setupViews()
setupViewModel()
setupRecyclerView()
}
private fun setupViews() {
binding.toolbar.title = getString(R.string.app_name)
binding.fab.setOnClickListener {
viewModel.intent.offer(MainViewModel.Intent.SearchClicked)
}
}
private fun setupViewModel() {
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
viewModel.feedFlow
.onEach(articlesAdapter::submitList)
.launchIn(lifecycleScope)
}
private fun setupRecyclerView() {
layoutManager = LinearLayoutManager(requireContext())
binding.recycler.layoutManager = layoutManager
binding.recycler.adapter = articlesAdapter
binding.recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val canScroll = recyclerView.canScrollVertically(-1)
binding.toolbar.isLifted = canScroll
}
})
}
private fun onSubscriptionClicked(article: Article) {
viewModel.intent.offer(MainViewModel.Intent.ItemClicked(article))
}
private fun handleState(state: MainViewModel.State) = when (state) {
NavigateToSearch -> findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
is FeedReady -> binding.progress.isVisible = false
Loading -> binding.progress.isVisible = true
is NavigateToArticle -> {
// works
// findNavController().navigate(
// R.id.articleFragment,
// bundleOf("articleLink" to state.link)
// )
}
}
}
And this is the XML for it:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
tools:context=".presentation.main.MainFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/bottomNav"
app:layout_constraintTop_toBottomOf="#id/toolbar"
tools:listitem="#layout/item_article_big" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/standard"
android:text="Add Feed"
android:textAlignment="center"
app:icon="#drawable/ic_add"
app:layout_constraintBottom_toTopOf="#id/bottomNav"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="#menu/menu_main" />
<tgo1014.news.presentation.customview.LiftableToolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="#+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="#id/toolbar"
app:layout_constraintTop_toBottomOf="#id/toolbar"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
the error is due to the lifecycle.
Replace this:
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
by
viewModel.state
.onEach(::handleState)
.launchIn(viewLifecycleOwner.lifecycleScope)

How to switch between tabs in BottomNavigationView programmatically in AndroidX?

I'm using the following activity layout with a fragment and a BottomNavigationView:
<LinearLayout 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">
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="#+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/navigation_bottom"
app:defaultNavHost="true"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/bottom_navigation_view"
app:menu="#menu/bottom_navigation_menu"/>
</LinearLayout>
And have defined three fragments in my navigation layout:
<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_bottom"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="fragments.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
//The other two fragments
</navigation>
Inside my activity, I'm using the following code:
navController = Navigation.findNavController(this, R.id.fragment);
BottomNavigationView bnw = findViewById(R.id.bottom_navigation_view);
NavigationUI.setupWithNavController(bnw, navController);
NavigationUI.setupActionBarWithNavController(this, navController);
And this my onSupportNavigateUp method:
#Override
public boolean onSupportNavigateUp() {
return Navigation.findNavController(this, R.id.fragment).navigateUp();
}
But when I press on the second icon (fragment) nothing happens. How to solve this issue? Thanks!
Solution 1:
In your menu(bottom_navigation_menu) item set same id as your fragment id generated by navigation graph like below:
<menu xmlns:android="http://schemas.android.com. /apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/firstFragment"
android:title="First fragment" />
<item
android:id="#+id/secondFragment"
android:title="Second fragmnet" />
<item
android:id="#+id/thirdFragment"
android:title="Third fragment" />
</menu>
You don't need to set by pro-grammatically because
NavigationUI.setupWithNavController(bnw, navController);
method will do the job.
Solution 2: Not recomended and Un-necessary
But if you want pro-grammatically then do like below:
private lateinit var navController: NavController
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
navController.navigate(R.id.firstFragment)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
navController.navigate(R.id.secondFragment)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
Toast.makeText(this,R.string.title_notifications,Toast.LENGTH_SHORT).show()
navController.navigate(R.id.thirdFragment)
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.nav_controller_fragment)
navView.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
}

Categories

Resources