8

I created an application with a bottom navigation bar and added four different nav graphs using google's advanced navigation example

After that I added a fifth graph called settings that had the settings fragment along with a global action

I added an include to that graph on each of the first four graphs

when I do something like findNavController(R.id.container).navigate(SettingsDirections.showSettings()) the app crashes because it cannot find the destination or the action

but when I copy the fragment and the global action inside each of those graphs and call the above (with that graph's directions) it works

am I missing something? doesn't include actually copy everything from the other graph the original?

Cruces
  • 3,029
  • 1
  • 26
  • 55

4 Answers4

18

It seems that the include tag does not actually include all of the nav graph including the global actions

so in case someone wants to do something similar here is how I did it

first I updated the settings navigation to include a dummy action to show settings like so:

<?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/settings"
    app:startDestination="@+id/settings_dest">

    <include app:graph="@navigation/questions" />

    <!--this is just so safeargs will create a SettingsNavigation.showSettings it is never actually used-->
    <action
        android:id="@+id/show_settings"
        app:destination="@id/settings_dest" />

    <fragment
        android:id="@+id/settings_dest"
        android:name="com.example.app.ui.fragments.SettingsFragment"
        android:label="@string/settings"
        tools:layout="@layout/fragment_settings" >

        <argument
            app:argType="com.example.app.ui.model.SettingsProfile"
            android:name="profile"
            app:nullable="false"/>
    </fragment>
</navigation>

and then on every nav graph that I wanted to use the show_settings action I would do by including this:

<include app:graph="@navigation/settings" />

<action
    android:id="@+id/show_settings"
    app:destination="@id/settings"
    app:enterAnim="@anim/fade_in"
    app:exitAnim="@anim/fade_out"
    app:popEnterAnim="@anim/fade_in"
    app:popExitAnim="@anim/fade_out" />

so now when I want to use directions to go to settings I do it like this

findNavController().navigate(SettingsDirections.showSettings(profile))

this will use the directions created in my settings.xml to execute the action in my current nav controller

it is important by the way the id of the action in the included file and the id of the action of the file including it to be the same

Filip Czaplicki
  • 189
  • 3
  • 12
Cruces
  • 3,029
  • 1
  • 26
  • 55
  • 4
    I used this solution too. But somehow I found it weird that we have to do it this way by adding action to every graph that need to call it. So much for global action. – Dark Leonhart Sep 25 '20 at 10:40
  • 1
    It's also good to mention that the destination of the action in the parent nav graph points to the id of the sub nav graph. And in the sub nav graph the destination of the action points to the actual destination. – Rule Oct 27 '20 at 06:15
  • *in case someone wants to do something similar* - this is not similar, this is literally the opposite of a global action – Tim Aug 11 '21 at 11:05
  • 1
    I used the same solution but I am not really satisfied with it. Lots of duplicate code imho. Is this really the way Google wants us to use Global actions when having different graphs? – Luigi_Papardelle Oct 16 '21 at 15:38
  • 1
    Any updates on this? – micstepper Jan 30 '22 at 10:11
  • I have a similar problem but my fragment is not one of the settings, it is part of a flow, so I don't feel like having to create a navigation graph to do this hack is a good solution. I would also like to know what google recommends in these situations. – Alberto Jun 06 '22 at 14:37
  • Can you please help me in my problem ? https://stackoverflow.com/q/76622858/11567530 – Ahmed Elsayed Jul 07 '23 at 14:26
  • I'm sorry but I haven't used navgraphs in a while now, I've switched to Compose, and frankly I don't remember much from navigation graphs via xmls – Cruces Jul 12 '23 at 10:25
0

Let me show you with an example

<navigation ...
  app:startDestination:"@id/nav_graph_one">

  <include app:graph:"@navigation/nav_graph_one"/>
  <include app:graph:"@navigation/nav_graph_two"/>

  <fragment
     android:id="@+id/aCommonFragment"
     .../>
  <action
     android:id="@+id/action_grobal_aCommonFragment
     app:destination="@+id/aCommonFragment"/>
</navigation>

Any where from your app you can now use

 findNavController().navigate(NavGraphDirections.actionGlobalACommonFragment)
Njuacha Hubert
  • 388
  • 3
  • 14
0

By copying nodes and actions from global actions graph to main graph in runtime it is possible to use global actions in all subgraphs and stay them in separate file.
main graph:

<navigation android:id="@+id/main_graph">
  <include app:graph="@navigation/subgraph1"/>
  <include app:graph="@navigation/subgraph2"/>
<navigation/>

global actions graph:

<navigation android:id="@+id/customer_dialogs">
  <action android:id="@+id/actionToShareCustomer"
     android:destionation="@id/shareCustomerDialog"/>
  <dialog android:id="@+id/shareCustomerDialog">
    <argument android:id="@+id/customerId" app:argType="long"/> 
  </dialog>
<navigation/>

programmatically set main_graph to navController:

override fun onCreate(savedInstanceState: Bundle?) {
  with(navHostFragment.findNavController()) {
    val mainGraph = navInflater.inflate(R.navigation.main_graph)
    val actionsGraph = navInflater.inflate(R.navigation.customer_dialogs)
    copyGraph(actionsGraph, mainGraph)    
    setGraph(mainGraph, null)
  }
}

fun copyGraph(from: NavGraph, to: NavGraph) {
  to.addAll(from)
  val actions = NavDestination::class.java.getDeclaredField("actions").let {
    it.isAccessible = true
    it.get(from)
  } as? SparseArrayCompat<NavAction>
  actions?.forEach { actionId, action -> to.putAction(actionId, action) }
}

usage of action from fragment of subgraph1:

private fun shareCustomer(customerId: Long) {
  findNavController().navigate(CustomerDialogsDirections.actionToShareCustomer(customerId))
}
chudo xl
  • 21
  • 3
-2

I created an extension function when setting the default animation

fun View.navigate(id: Int, navOptions: NavOptions? = null){
  var updatedNavOptions = navOptions

  if(updatedNavOptions == null){
    updatedNavOptions = navOptions {
        anim {
            enter = R.anim.slide_in_right
            exit = R.anim.slide_out_left
            popEnter = R.anim.slide_in_left
            popExit = R.anim.slide_out_right
        }
    }
}

    this.findNavController().navigate(id, null, updatedNavOptions)
}

In my fragment just do so:

my_view.click { view?.navigate(R.id.how_referral_program_works) }
Edhar Khimich
  • 1,468
  • 1
  • 17
  • 20