Reputation: 4295
I added a nullable argument to my start destination:
<?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/startDest">
<fragment android:id="@+id/startDest"
android:name="com.myapp.MyStartFragment"
android:label="Start"
tools:layout="@layout/fragment_start">
<argument
android:name="dataObject"
app:argType="com.myapp.MyDataObject"
android:defaultValue="@null"
app:nullable="true"/>
...
</fragment>
...
</navigation>
But when I load my app, I get the following exception:
java.lang.IllegalStateException: Fragment MyStartFragment{a4ffd1f (ca52d4dc-ff36-4a93-8ebf-f11af7b7d5aa) id=0x7f080145} has null arguments
at com.myapp.MyStartFragment$$special$$inlined$navArgs$1.invoke(FragmentNavArgsLazy.kt:42)
at com.myapp.MyStartFragment$$special$$inlined$navArgs$1.invoke(Unknown Source:0)
at androidx.navigation.NavArgsLazy.getValue(NavArgsLazy.kt:44)
at androidx.navigation.NavArgsLazy.getValue(NavArgsLazy.kt:34)
at com.myapp.MyStartFragment.getArgs(Unknown Source:27)
at com.myapp.MyStartFragment.onAttach(MyStartFragment.kt:85)
And the exception is triggered by this piece of code in MyStartFragment:
private val args: MyStartFragmentArgs by navArgs()
override fun onAttach(context: Context) {
super.onAttach(context)
val title = if(this.args.dataObject == null) getString(R.string.start_list_title) else this.args.dataObject!!.name
...
}
And here is the code for MyDataObject:
@Parcelize
data class MyDataObject (
val id: String,
val name: String,
val externalIdentifier: String
val type: MyDataEnumType,
var responsibleUser: SomeOtherParcelableClass?
): Parcelable
What I don't understand is that my start destination doesn't get passed arguments properly by the navigation controller. Am I missing something here?
Upvotes: 6
Views: 5479
Reputation: 867
If you create a fragment with a bundle, but without NavController.navigate, and you still want to keep "by navArgs()" in destination fragment e.g because you want to use it in launchFragmentInContainer when testing, then use try/catch. The IllegalStateException is thrown before you start checking nullable type.
private fun updateArguments(){
this.title = try{
val args: DestinationFragmentArgs by navArgs()
if (args.dataObject?.name == ""){
// default value provided so check whether bundle was used
arguments?.getString("name") ?: ""
}else {
args?.venueId
}
}catch (ex: Exception){
// fragment created without using NavController.navigate
arguments?.getString("venueId") ?: ""
}
}
Room is very fast, so retrieving an object from the id as an argument should not be an issue, so try using primitive types for your arguments if possible, otherwise use Parcelable or Serialize for inexpensive objects.
Upvotes: 2
Reputation: 13607
Hello I am assuming you want to achieve something like below
BeforeFragment --arg--> StartFragment --> AfterFragment
This flows are similar first time user, returning user flows. Here BeforeFragment
is last fragment in login_nav_graph
nested graph. StartFragment
is the starting destination of main_nav_graph
. StartFragment
is the first screen returning user sees.
So in BeforeFragment
you may set args as follows
val userJohn:User = User(34, "John", 645, UserType.TYPE2, Guardian("Mike"))
val action = BeforeFragmentDirections.actionGlobalStart(userJohn)
findNavController().navigate(action)
and in StartFragment
you may do following as you already did
title = if(this.args.user == null)
getString(R.string.user_name) // mocks loading saved user name
else
this.args.user?.name // when user is first time user read from passed args
Sample Repo can be found here
My best guess
This issue is due to bug in older navigation version, so use 2.2.0-alpha01
which is I am using in the sample repo.
In order to fix errors that occurs when moved to new navigation version in you module gradle file add following
android {
...
kotlinOptions {
jvmTarget = "1.8" // set your Java version here
}
}
This fixes the error
Cannot inline byte code ...
Keep in mind passing complex objects as arguments is not recommended. Quoting from docs.
In general, you should strongly prefer passing only the minimal amount of data between destinations. For example, you should pass a key to retrieve an object rather than passing the object itself, as the total space for all saved states is limited on Android. If you need to pass large amounts of data, consider using a ViewModel as described in Share data between fragments.
If this fixes your issue please confirm the answer, since I spent lot of time preparing this post.
Upvotes: 7