Search code examples
androidandroid-activityandroid-notificationsandroid-bundleandroid-architecture-navigation

Get argument from navigation graph


I have a nav graph as follows:

<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/main_graph"
    app:startDestination="@id/jobsFragment">

    <fragment
        android:id="@+id/jobsFragment"
        android:name=".JobsFragment"
        android:label="JobsFragment">

        <action
            android:id="@+id/action_jobsFragment_to_newJobDetailsActivity"
            app:destination="@id/newJobDetailsActivity" />

    </fragment>

    <fragment
        android:id="@+id/historyFragment"
        android:name=".HistoryFragment"
        android:label="HistoryFragment" />

    <fragment
        android:id="@+id/profileFragment"
        android:name=".ProfileFragment"
        android:label="ProfileFragment" />

    <activity
        android:id="@+id/newJobDetailsActivity"
        android:name=".NewJobDetailsActivity"
        android:label="activity_new_job_details"
        tools:layout="@layout/activity_new_job_details">

        <!-- This is what I want a reference to -->
        <argument
            android:name="jobId"
            app:argType="string" />

    </activity>

</navigation>

Goal: retrieve the value of newJobDetailsActivity's argument name, jobId, programmatically without needing a reference to an action. With the current API, I can set the jobId through an action without needing to know the string name literal. This is good, but it's not ideal for cases when I want the caller to be able to navigate to a destination without needing to know which component the caller is attached to. This is easier to explain with code. Let's take a look at two scenarios that I'm trying to solve for.

Scenario A

Let's assume I have a RecyclerView.ViewHolder that has some code in it that navigates from a Fragment called JobsFragment to an Activity called NewJobDetailsActivity when clicked on.

itemView.setOnClickListener {
    itemView.findNavController().navigate(
        JobsFragmentDirections.ActionJobsFragmentToNewJobDetailsActivity(jobId)
    )
}

This is great if the RecyclerView.ViewHolder only ever exists in the JobsFragment, but if I want to reuse it from an additional Fragment or Activity in the future, my code flops because the view holder needs to reference JobsFragmentDirections's action in order to pass the jobId argument.

Scenario B

Let's assume I need to create a PendingIntent for a notification, and I want to do so using the Navigation Architecture API.

NavDeepLinkBuilder(context)
    .setGraph(R.navigation.main_graph)
    .setDestination(R.id.newJobDetailsActivity)
    .setArguments(Bundle().apply {
        putString("jobId", jobId)
    })
    .createPendingIntent()

In this case, I don't need a reference to an action, but the situation is actually worse because I can't find a reference to the argument name in the main_graph, so I'm forced to specify it using a string literal. Yuck.

tl;dr: Is anyone aware of a way to improve these two scenarios? Is there a way to programmatically get a reference to an argument from a nav graph?

Dependencies: android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha06, android.arch.navigation:navigation-ui-ktx:1.0.0-alpha06

Plugin: androidx.navigation.safeargs


Solution

  • For the second scenario you can use something like this:

    val args = JobsFragmentArgs.Builder(id).build().toBundle()
    NavDeepLinkBuilder(context)
        .setGraph(R.navigation.main_graph)
        .setDestination(R.id.newJobDetailsActivity)
        .setArguments(args)
        .createPendingIntent()
    

    For the first scenario, personally, I would recommend passing the on click listener from the fragment/activity that creates the adapter and setting the listener to the view holder on bind. Then you can implement on click differently for other fragments/activities.