I have the problem that my sample app crashes when I deeplink from one graph part to the other one.
My structure is simple:
Two tabs with own graphs which have each two possible fragments
When I navigate to a detail page (in my sample a book) this just works from my "Shelf" but not from the home screen.
Here is my full 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/sample"
app:startDestination="#id/tab_home">
<navigation
android:id="#+id/tab_home"
app:startDestination="#id/frag_home">
<fragment
android:id="#+id/frag_home"
android:name="eu.rekisoft.android.navbug.HomeFragment"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/cause_bug"
app:destination="#id/frag_hint"/>
<action
android:id="#+id/causeCrash"
app:destination="#id/frag_book">
<argument
android:name="name"
app:argType="string"/>
</action>
</fragment>
<fragment
android:id="#+id/frag_hint"
android:name="eu.rekisoft.android.navbug.HintFragment"
android:label="Hint"
tools:layout="#layout/fragment_hint"/>
</navigation>
<navigation
android:id="#+id/tab_books"
app:startDestination="#id/frag_shelf">
<fragment
android:id="#+id/frag_shelf"
android:name="eu.rekisoft.android.navbug.ShelfFragment"
android:label="Shelf Fragment"
tools:layout="#layout/fragment_shelf">
<action
android:id="#+id/open_book"
app:destination="#id/frag_book">
<argument
android:name="name"
app:argType="string"/>
</action>
</fragment>
<fragment
android:id="#+id/frag_book"
android:name="eu.rekisoft.android.navbug.BookFragment"
android:label="Book Fragment"
tools:layout="#layout/fragment_book">
<argument
android:name="name"
app:argType="string"/>
</fragment>
</navigation>
</navigation>
Here is my crash:
java.lang.IllegalArgumentException: Navigation destination eu.rekisoft.android.navbug:id/frag_book referenced from action eu.rekisoft.android.navbug:id/causeCrash cannot be found from the current destination Destination(eu.rekisoft.android.navbug:id/frag_home) class=eu.rekisoft.android.navbug.HomeFragment
at androidx.navigation.NavController.navigate(NavController.kt:1527)
at androidx.navigation.NavController.navigate(NavController.kt:1464)
at androidx.navigation.NavController.navigate(NavController.kt:1922)
at eu.rekisoft.android.navbug.HomeFragment.onViewCreated$lambda-2$lambda-1(HomeFragment.kt:23)
at eu.rekisoft.android.navbug.HomeFragment.$r8$lambda$q53N5QXeJ-lbbpd7MjwkcKbt9_4(Unknown Source:0)
at eu.rekisoft.android.navbug.HomeFragment$$ExternalSyntheticLambda1.onClick(Unknown Source:2)
Very clear error message, but I don't know how to fix this.
If you want to checkout my code check this GitHub repo.
Related
I am trying to add a fragment which has implementation done as part of an external framework. Need to include/reference the fragment in the running application. How this can be done?
below is the navigation XML for reference.
<?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/main"
app:startDestination="#id/officeFragment">
<fragment
android:id="#+id/officeFragment"
android:name="com.sample.office.OfficeFragment"
android:label="#string/office">
<argument
android:name="officeId"
app:argType="string"
app:nullable="false" />
<action
android:id="#+id/viewEmployees"
app:destination="#id/profileListFragment"/>
</fragment>
<fragment
android:id="#+id/profileListFragment"
android:name="com.externallibrary.component.people.ui.profilelist.ProfileListFragment"
android:label="#string/people">
<argument
android:name="officeId"
android:defaultValue="#null"
app:argType="string"
app:nullable="true" />
</fragment>
</navigation>
I am using navigation architecture component in my application.
Suppose I have fragment A as start destination.
I have push fragment B and Fragment C via navigation controller.
My navigation back stack is A-> B-> C.
Then I navigate to Fragment A with below action
<action
android:id="#+id/fragment_A_type"
app:destination="#id/fragment_a"
app:popUpTo="#id/fragment_a"
app:popUpToInclusive="true" />
Now My navigation back stack should be A.
But if check
parentFragmentManager.fragments back stack is
A->A
Also the swipeBackDismiss of activity is not working.
my navigation.xml file
<?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">
<fragment
android:id="#+id/FirstFragment"
android:name="com.example.wearexample1.FirstFragment"
android:label="#string/first_fragment_label"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_FirstFragment_to_SecondFragment"
app:destination="#id/SecondFragment" />
</fragment>
<fragment
android:id="#+id/SecondFragment"
android:name="com.example.wearexample1.SecondFragment"
android:label="#string/second_fragment_label"
tools:layout="#layout/fragment_second">
</fragment>
<action
android:id="#+id/action_global_activity_type"
app:destination="#id/FirstFragment"
app:popUpTo="#id/FirstFragment"
app:popUpToInclusive="true" />
</navigation>
I had the main navigation of my app set up nicely using SafeArgs. This was my nav_graph.xml:
<navigation
android:id="#+id/nav_main"
app:startDestination="#+id/fragment1">
<fragment
android:id="#+id/fragment1"
android:name="org.example.Fragment1"
android:label="Fragment 1">
<action
android:id="#+id/fragment1_to_fragment2"
app:destination="#id/fragment2" />
</fragment>
<fragment
android:id="#+id/fragment2"
android:name="org.example.Fragment2"
android:label="Fragment 2">
<argument
android:name="ARG_CATEGORY_ID"
app:argType="long"/>
<action
android:id="#+id/fragment2_to_fragment3"
app:destination="fragment3" />
</fragment>
<fragment
android:id="#+id/fragment3"
android:name="org.example.Fragment3"
android:label="Fragment 3">
<argument
android:name="ARG_ITEM_ID"
app:argType="long"/>
</fragment>
</navigation>
And, using SafeArgs, I could happily navigate from Fragment2 to Fragment3 using:
NavDirections navDirections = Fragment2Directions.fragment2ToFragment3(myItemId);
navController.navigate(navDirections);
However, I then needed to add deep linking capability from notifications (having a backstack with arguments, as explained here), so I added a sub <navigation /> element to the end of nav_graph.xml - which became:
<navigation
android:id="#+id/nav_main"
app:startDestination="#+id/fragment1">
<fra8gment
android:id="#+id/fragment1"
android:name="org.example.Fragment1"
android:label="Fragment 1">
<action
android:id="#+id/fragment1_to_fragment2"
app:destination="#id/fragment2" />
</fragment>
<fragment
android:id="#+id/fragment2"
android:name="org.example.Fragment2"
android:label="Fragment 2">
<argument
android:name="ARG_CATEGORY_ID"
app:argType="long"/>
<action
android:id="#+id/fragment2_to_fragment3"
app:destination="fragment3" />
</fragment>
<fragment
android:id="#+id/fragment3"
android:name="org.example.Fragment3"
android:label="Fragment 3">
<argument
android:name="ARG_ITEM_ID"
app:argType="long"/>
</fragment>
<!-- This is the new sub navigation block that provides a backstack-with-arguments. -->
<navigation
android:id="#+id/nav_notification"
app:startDestination="#id/fragment2Notif">
<fragment
android:id="#+id/fragment2Notif"
android:name="org.example.Fragment2"
android:label="Fragment 2">
<action
android:id="#+id/fragment2Notif_to_fragment3Notif"
app:destination="#id/itemsGraph" />
</fragment>
<navigation
android:id="#+id/itemsGraph"
app:startDestination="#id/fragment3Notif">
<argument
android:name="ARG_ITEM_ID"
app:argType="long"/>
<fragment
android:id="#+id/fragment3Notif"
android:name="org.example.Fragment3"
android:label="Fragment 3">
<argument
android:name="ARG_ITEM_ID"
app:argType="long"/>
</fragment>
</navigation>
</navigation>
</navigation>
Notice that the file now contains two occurrences of org.example.Fragment2.
This has brought me a new problem in that the second occurrence of org.example.Fragment2 (ie, fragment2Notif) has resulted in the original Fragment2Directions.fragment2ToFragment3(...) method being no longer available.
It's been replaced with Fragment2Directions.fragment2NotifToFragment3Notif(...) which works fine when in the notification part of my app but, when called from the main part of my app, as expected, results in:
IllegalArgumentException: navigation destination
org.example:id/fragment2Notif_to_fragment3Notif is unknown to this
NavController
It seems SafeArgs has overwritten the first Fragment2Directions object, meaning the only method it has is fragment2NotifToFragment3Notif(...).
So, how do I get the old fragment2ToFragment3(...) method back?
I expect I could solve the problem by subclassing Fragment2 to effectively duplicate it with a different name, Fragment2Notif, then using Fragment2 in my main nav and Fragment2Notif in the sub nav, but is there a more elegant/preferred way?
Looking through your nav_graph.xml file, I think the idea for nested graphs(series of destinations grouped within the parent navigation graph, in this case with id nav_main) is to add deep linking capability from notifications (having a backstack with arguments).
Other than this, with the assumption that the fragment2ToFragment3(...) is overwritten to fragment2NotifToFragment3Notif(...) by SafeArgs plugin, there is no significant change as one can still navigate to Fragment3 using fragment2NotifToFragment3Notif(...) since the two nested navigation graphs combined have same structural content as the parent navigation graph except for id attribute of the <fragment> tag.
So, in essence using fragment2NotifToFragment3Notif(...) instead of fragment2ToFragment3(...) won't hurt for this case.
I want to migrate my project to use navigation components.
In my Activity there is a bottom navigation, which navigates through different instances of the same Fragment (with different arguments).
Here explained about bottomNavigation support.
But is it possible to reuse same Fragment in the same navigation graph, with different IDs and params?
I can't find way in google documentations.
You should be able to define your nav_graph with multiple destinations and reuse the same fragment. Something like this,
<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_tab1">
<fragment
android:id="#+id/navigation_tab1"
android:name="com.myapp.MyFragment"
android:label="Tab1">
<action
android:id="#+id/action_goto_page1"
app:destination="#id/navigation_page1" />
<argument
android:name="tab1_arg1"
android:defaultValue="Default"
app:argType="string" />
</fragment>
<fragment
android:id="#+id/navigation_tab2"
android:name="com.myapp.MyFragment"
android:label="Tab2">
<action
android:id="#+id/action_goto_page2"
app:destination="#id/navigation_page2" />
<argument
android:name="tab2_arg1"
android:defaultValue="Default"
app:argType="string" />
</fragment>
<fragment
android:id="#+id/navigation_tab3"
android:name="com.myapp.MyFragment"
android:label="Tab3">
<action
android:id="#+id/action_goto_page3"
app:destination="#id/navigation_page3" />
<argument
android:name="tab3_arg1"
android:defaultValue="Default"
app:argType="string" />
</fragment>
</navigation>
However, it's best to refactor your code to have multiple Fragments (each doing one thing) for better maintenance and clean code.
I have this in app gradle:
apply plugin: 'androidx.navigation.safeargs'
implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0'
and this in the project gradle:
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
the navigation graph:
<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_graph"
app:startDestination="#id/loginPhoneNumberFragment">
<fragment
android:id="#+id/loginPhoneNumberFragment"
android:name="...fragments.LoginPhoneNumberFragment"
android:label="#string/login_activity_title"
tools:layout="#layout/fragment_login_phone_number">
<action
android:id="#+id/action_loginPhoneNumberFragment_to_loginCodeFragment"
app:destination="#id/loginCodeFragment">
<argument
android:name="prefix"
app:argType="string" />
<argument
android:name="phone_number"
app:argType="string" />
</action>
</fragment>
<fragment
android:id="#+id/loginCodeFragment"
android:name="...LoginCodeFragment"
android:label="#string/login_activity_title"
tools:layout="#layout/fragment_login_code" />
</navigation>
LoginPhoneNumberFragment:
val action = LoginPhoneNumberFragmentDirections.actionLoginPhoneNumberFragmentToLoginCodeFragment(prefix, phoneNumber)
view?.findNavController()?.navigate(action)
LoginCodeFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val prefix = LoginCodeFragmentArgs.fromBundle(arguments).prefix //LoginCodeFragmentArgs is not recognized
}
In the LoginPhoneNumberFragment it creates the "LoginPhoneNumberFragmentDirections" class, but on the destination class, LoginCodeFragment, it doesn't recognize "LoginCodeFragmentArgs".
Can someone please tell me what am I missing?
(I cleaned and rebuilded, and tried Invalidate caches...)
Ok, so after a lot of searching I found out my mistake -
the arguments should be on the Destination fragment, and not on the starting one:
<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_graph"
app:startDestination="#id/loginPhoneNumberFragment">
<fragment
android:id="#+id/loginPhoneNumberFragment"
android:name="...fragments.LoginPhoneNumberFragment"
android:label="#string/login_activity_title"
tools:layout="#layout/fragment_login_phone_number">
<action
android:id="#+id/action_loginPhoneNumberFragment_to_loginCodeFragment"
app:destination="#id/loginCodeFragment">
</action>
</fragment>
<fragment
android:id="#+id/loginCodeFragment"
android:name="...fragments.LoginCodeFragment"
android:label="#string/login_activity_title"
tools:layout="#layout/fragment_login_code" >
<argument
android:name="prefix"
app:argType="string"
android:defaultValue="888" />
<argument
android:name="phone_number"
app:argType="string"
android:defaultValue="88888888"/>
</fragment>
</navigation>
You can also add it manually via the navigation graph design - press on the destination fragment and press "+" on the arguments section, it will add it to the text file.
The argument should be in the destination fragment as follows instead of inside the action in source fragment
<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_graph"
app:startDestination="#id/loginPhoneNumberFragment">
<fragment
android:id="#+id/loginPhoneNumberFragment"
android:name="...fragments.LoginPhoneNumberFragment"
android:label="#string/login_activity_title"
tools:layout="#layout/fragment_login_phone_number">
<action
android:id="#+id/action_loginPhoneNumberFragment_to_loginCodeFragment"
app:destination="#id/loginCodeFragment"/>
</fragment>
<fragment
android:id="#+id/loginCodeFragment"
android:name="...LoginCodeFragment"
android:label="#string/login_activity_title"
tools:layout="#layout/fragment_login_code">
<argument
android:name="prefix"
app:argType="string" />
<argument
android:name="phone_number"
app:argType="string" />
</fragment>
</navigation>