When I add the same fragment to second navigation graph, don´t recognize the Action Class - android

I need to add the same fragment to different navigation graphs. This is piece of code of first graph (mobile_navigation)
<fragment
android:id="#+id/nav_contactanos"
android:name="com.engie.mexico.micuenta.Fragments.FragmentContactanosCliente"
tools:layout="#layout/fragment_contactanos_cliente" >
<action
android:id="#+id/action_nav_contactanos_to_nav_aviso_privacidad"
app:destination="#id/nav_aviso_privacidad" />
<action
android:id="#+id/action_nav_contactanos_to_nav_terminos_condiciones"
app:destination="#id/nav_terminos_condiciones" />
<action
android:id="#+id/action_nav_contactanos_to_nav_mensaje_usuario"
app:destination="#id/nav_mensaje_usuario" />
</fragment>
The second fragment of code in second navigation graph (navigation_inicial)
<fragment
android:id="#+id/nav_contactanos_cliente"
android:name="com.engie.mexico.micuenta.Fragments.FragmentContactanosCliente"
tools:layout="#layout/fragment_contactanos_cliente" >
<action
android:id="#+id/action_nav_contactanos_cliente_to_nav_mensaje_usuario"
app:destination="#id/nav_mensaje_usuario" />
<action
android:id="#+id/action_nav_contactanos_cliente_to_nav_terminos_condiciones"
app:destination="#id/nav_terminos_condiciones" />
<action
android:id="#+id/action_nav_contactanos_cliente_to_nav_aviso_privacidad"
app:destination="#id/nav_aviso_privacidad" />
</fragment>
When I try to MakeProject, the build output shows me the error :
C:\Android\MiCuenta\app\src\main\java\com\engie\mexico\micuenta\Fragments\FragmentContactanosCliente.java:478: error: cannot find symbol
FragmentContactanosClienteDirections.ActionNavContactanosToNavMensajeUsuario action = symbol: class ActionNavContactanosToNavMensajeUsuario
location: class FragmentContactanosClienteDirections
But when I comment the second piece of code (navigation_inicial), the problem disappears.
The thing is that I need the second piece of code because I need to recall fragment and share with it bundles, with other information and show different things...
How can achieve this?
I also share the design of graphs:
mobile_navigation
mobile_navigation.xml (design)
navigation_inicial
navigation_inicial.xml (design)

The name of the Directions class is based on the name of the Fragment and there can only be one instance of a particular class at a time. As per this issue with Safe Args, there is no warning when you are overwriting one Directions class from one from another graph - the last one wins. This is why reusing the same fragment in a different graph invalidates the Directions class from the first graph.
Of course, if your second graph's fragment has different actions, different arguments, or anything at all different about it, it should have a different fragment class as well - if your single fragment class were to use arguments or actions from another graph, they would fail as they would not exist in that graph.
As mentioned in that bug:
For a destination with an android:name, you'd want to include that android:name in only one place in your graph, referencing that shared destination from all of the places that need access to it (as a destination can access any sibling destinations of their parent graph).

Related

Navigate to different fragments in another nav graph

I have to navigate from fragment in module 1 and navgraph #1 (let's call it first_nav_graph) to different fragments in another module and another navgraph #2 (let's call it second_nav_graph).
When I navigate to startDestination of second_nav_graph it works correctly - I just include second_nav_graph to first_nav_graph and create action:
<include app:graph="#navigation/second_nav_graph" />
...
<dialog
android:id="#+id/card_menu_fragment"
android:name="ru.bank.mobile.presentation.ui.product_menus.CardMenuFragment"
android:label="CardMenuFragment"
tools:layout="#layout/fragment_menu">
<action
android:id="#+id/actionCardMenuFragmentToSecondNavGraph"
app:destination="#id/second_nav_graph"/>
So we can easily navigate to startDestination of second_nav_graph. The problem is that I need to navigate to two different fragments of second_nav_graph. I added such actions to first_nav_graph:
<include app:graph="#navigation/second_nav_graph" />
...
<dialog
android:id="#+id/card_menu_fragment"
android:name="ru.bank.mobile.presentation.ui.product_menus.CardMenuFragment"
android:label="CardMenuFragment"
tools:layout="#layout/fragment_menu">
<action // navigates correctly
android:id="#+id/actionCardMenuFragmentToSecondNavGraph"
app:destination="#id/second_nav_graph"/>
<action // app crashes
android:id="#+id/actionCardMenuFragmentToIncreaseLimitFragment"
app:destination="#id/increaseLimitFragment" />
<action // app crashes
android:id="#+id/actionCardMenuFragmentToIncreaseLimitStatusFragment"
app:destination="#id/increaseLimitStatusFragment" />
But when I push button that navigates to specific fragment in second_nav_graph (with findNavController().navigate()) my app crashes with exception:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: ru.otpbank.mobile, PID: 11072
java.lang.IllegalArgumentException: Navigation destination ru.otpbank.mobile:id/increaseLimitFragment referenced from action ru.otpbank.mobile:id/actionCardMenuFragmentToIncreaseLimitFragment cannot be found from the current destination Destination(ru.otpbank.mobile:id/card_menu_fragment) label=CardMenuFragment
second_nav_graph.xml 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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/second_nav_graph"
app:startDestination="#id/increaseLimitFragment">
<fragment
android:id="#+id/increaseLimitFragment"
android:name="com.example.credit_limit_increase.limit_increase_application.IncreaseLimitFragment"
android:label="IncreaseLimitFragment" />
<fragment
android:id="#+id/increaseLimitStatusFragment"
android:name="com.example.credit_limit_increase.limit_increase_status.IncreaseLimitStatusFragment"
android:label="fragment_increase_limit_status"
/>
</navigation>
Could you please help me to understand the reason of this behavior and any ways to fix it?
I found in documentation that the only way to access another graph is through its startDestination:
The nested graph encapsulates its destinations. As with a root graph,
a nested graph must have a destination identified as the start
destination. Destinations outside of the nested graph, such as those
on the root graph, access the nested graph only through its start
destination.
So it seems like there are no ways to navigate to different fragments of a nested graph from another graph.

Android fragment navigation: how to navigate to an arbitrary fragment programmatically

I am learning to use Android fragment navigation.
Now I can follow the sample to use ID to navigate to a specific target fragment defined in the navigation graph. Like following:
<fragment android:id="#+id/a"
android:name="com.example.myapplication.FragmentA"
android:label="a"
tools:layout="#layout/a">
<action android:id="#+id/action_a_to_b"
app:destination="#id/b"
app:enterAnim="#anim/nav_default_enter_anim"
app:exitAnim="#anim/nav_default_exit_anim"
app:popEnterAnim="#anim/nav_default_pop_enter_anim"
app:popExitAnim="#anim/nav_default_pop_exit_anim"/>
</fragment>
viewTransactionsButton.setOnClickListener { view ->
view.findNavController().navigate(R.id.viewTransactionsAction)
}
However, I have a special use case that I want to programmatically navigate to a destination fragment, which is not defined in the navi graph. How can I achieve it like following:
itemView.setOnclickListener { view ->
val targetFragmentClass = getTargetFragmentClass(view)
//How can I findNavController().navigate() to created targetFragmentClass instance?
}
I checked the document of navigation controller, all versions of its navigate() method need some resource ID which means I have to define the action or destination in the XML. But I want something like the legacy fragment manger APIs, which I can instantiate any fragment class and create a transaction to apply it.
So, my questions is:
Can I use navigation component to navigate to any arbitrary fragment purely programmatically?
If I have to use fragment manager/transaction to deal with this case, can I make it work with the navigation controller together?
Thanks a lot.

How to reuse a fragment in different navigation graphs with safe args enabled?

I'm trying to reuse a fragment in different navigation graphs with safe args enabled. I noticed that if the actions are different I get a compilation error. This is because the xxxFragmentDirections autogenerated code will only generate one of the actions.
In nav_graph_1.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/next_action"
app:destination="#+id/dest_one" />
</fragment>
...
In nav_graph_2.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/other_action"
app:destination="#+id/other_dest" />
</fragment>
...
A simple use case: A bank app that has two flows: withdraw and deposit, therefore you could have two nav graphs. You could have an AmountFragment where you could just enter a number and this could be reused to either withdraw or deposit. However, depending on the flow, the actions/destinations could be different.
Then, how would it be possible to reuse this fragment?
Use navigate() with bundle instead of actions in edge cases. Don’t call
findNavController().navigate(FragmentDirections.goToDetailFragment(id))
but instead use
findNavController().navigate(R.id.DetailFragment, bundleOf("id" to 5))
This way you don’t rely on the generated direction but can still use the Navigation and SafeArgs features of the DetailFragment.
https://code.allaboutapps.at/articles/android-jetpack-navigation-pitfalls/#reuse-fragments-in-multiple-navigation-graphs
This can be achieved by providing the same action ID in both the navigation graph.
In nav_graph_1.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/next_action"
app:destination="#+id/dest_one" />
</fragment>
...
In nav_graph_2.xml:
<navigation
...
<fragment
android:id="#+id/myFragment"
android:name="com.example.android.MyFragment">
<action
android:id="#+id/next_action"
app:destination="#+id/other_dest" />
</fragment>
...
In fragment, navigation can be performed like
NavHostFragment.findNavController(<myFragment>).navigate(R.id.next);
It might not be what you ask, but I faced the problem o re-usability of code in a fragment that I needed in different navigation graphs.
The fragment class that needs to be reused should be declared as abstract:
abstract class ReusableFragment(val type: ReusableEnum): Fragment(){
enum class ReusableEnum{Type1, Type2}
// the rest of your logic will use
// 'type' variable in when() blocks to determine
// specific logic for each case
}
class Type1Fragment: ReusableFragment(ReusableEnum.Type1)
class Type2Fragment: ReusableFragment(ReusableEnum.Type2)
This way, Type1Fragment and Type2Fragment are available in NavigationGraph as independent fragments.

Unable to navigate to a destination in other graph

Using Android Navigation component in our code with SAMF (Single Activity, Multiple Fragments) approach for each functionality. Say each functionality will have own navigation graph intended to navigate between them.
e.g., the graphs are graph_a, graph_b, graph_c with its own fragments and designed to navigate as below:
graph_a >> graph_b >> graph_c
and it works as expected.
Now, there are scenarios where a Fragment(frag_b) might need to navigate another one in different navigation graph like
frag_b(graph_b) >> frag_c(graph_a)
I found the optimal solution, in this case, could be navigate using URI and tried below.
val deepLink = Uri.parse("app://login/user-signup")
findNavController().navigate(deepLink)
and added the following deeplink for to be called fragment in the respective graph.
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/graph_login_flow"
app:startDestination="#id/userSignInFragment">
...
<fragment
android:id="#+id/userSignUpFragment"
android:name="com.app.login.ui.UserSignUpFragment"
android:label="EmailSignUpFragment"
tools:layout="#layout/fragment_user_sign_up">
<action
android:id="#+id/action_userSiUpFrag_to_emailVeriFrag"
app:destination="#id/emailVerificationFragment" />
<deepLink
android:id="#+id/deepLink_to_userSignUpFragment"
app:uri="app://login/user-signup" />
</fragment>
<include app:graph="#navigation/graph_home_flow" />
...
</navigation>
As per Android documentation, can navigate to any destination regardless of graphs but my attempt results in the following error.
Is there any better approach?
java.lang.IllegalArgumentException: navigation destination with deepLink app://login/user-signup is unknown to this NavController
at androidx.navigation.NavController.navigate(NavController.java:916)
at androidx.navigation.NavController.navigate(NavController.java:894)
at androidx.navigation.NavController.navigate(NavController.java:880)
at com.app.home.profile.ProfileFragment.onViewCreated(ProfileFragment.kt:82)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:298)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1232)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2390)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2125)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2081)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1977)

How to share common actions (or activities) between multiple navigation graphs?

I am using navigation library in my Android application with multiple activities and I would like to know, whether there is a way to share some actions (which do open certain page with predefined arguments) between multiple navigation graphs.
It goes like this:
There exists ActivityB which needs arguments a, b, c in its navigation action.
ActivityA has NavGraphA which opens ActivityB.
ActivityC has NavGraphC which also opens ActivityB.
Now I would like to use action which opens ActivityB in both NavGraphA and NavGraphC, preferably without duplicating the activity, its arguments and action itself in both graphs (e.g. some sort of include?).
I know that it is possible to define ActivityB as activity element in both graphs, but then I need to again define action with the same arguments all over again.
I will need to do something like this in both graphs:
<fragment
android:id="#+id/xyFragment"
android:name="x.y.fragment"
android:label="xyFragment" >
<action
android:id="#+id/action_xyFragment_to_ActivityB"
app:destination="#id/activityB" />
</fragment>
<activity
android:id="#+id/activityB"
android:name="x.y.ActivityB"
android:label="ActivityB" >
<argument
android:name="a"
app:argType="argument.A" />
<argument
android:name="b"
app:argType="argument.B" />
<argument
android:name="c"
app:argType="argument.C" />
</activity>
As you can see - I can either wrap those arguments into one class, which will reduce the amount of arguments, but the original problem will still be there.
Or I can just duplicate it with different ids.
Maybe there is a way to somehow include shared global action across multiple graphs?
You can use a global action to create a common action that multiple destinations can use.
Add ActivityB either in NavGraphA or NavGraphC, then make it as global by right click -> add action -> global on navigation design view.
So both ActivityA & ActivityC can navigate to ActivityB by using it id: eg. R.id.action_global_activityB
Refer Global actions for more details.

Categories

Resources