15

I'm having some difficulties sending an argument through a PendingIntent of a notification using NavDeepLinkBuilder. I'm able to get the destination Activity to launch by clicking the notification, but the Activity's Intent doesn't contain the argument value that I passed it through the NavDeepLinkBuilder. The Intent instead returns the defaultValue that I set in the nav graph - "noJobId".

Notification creation:

val notification =
    NotificationCompat.Builder(context, context.getString(R.string.notification_channel_id_new_job))
        ...
        .setContentIntent(
            NavDeepLinkBuilder(context)
                .setComponentName(NewJobDetailsActivity::class.java)
                .setGraph(R.navigation.main_graph)
                .setDestination(R.id.newJobDetailsActivity)
                .setArguments(
                    NewJobDetailsActivityArgs.Builder()
                        .setJobId(event.jobId)
                        .build()
                        .toBundle()
                )
                .createPendingIntent()
        )
        .build()

notificationManager.notify(notificationId, notification)

The context used in the notification is the FirebaseMessagingService.

Destination Activity onCreate():

val jobId: String = NewJobDetailsActivityArgs.fromBundle(intent?.extras).jobId

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

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

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

    </fragment>

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

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

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

        <argument
            android:name="jobId"
            android:defaultValue="noJobId" // just for testing
            app:argType="string" />

    </activity>

</navigation>

Has anyone else run into this issue? I have a feeling it's a bug with the Navigation component, but I'm not 100% sure yet. Curious if there's something I'm missing here.

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

Ryan
  • 3,414
  • 2
  • 27
  • 34

3 Answers3

10

I posted this issue on Google's public issue tracker, and I received the following response:

NavDeepLinkBuilder passes its args to a NavController to deep link into a specific destination. Activity destinations are really more exit points from a navigation graph than something that can/should be deep linked to.

Source: https://issuetracker.google.com/issues/118964253

The author of that quote recommends using TaskStackBuilder instead of NavDeepLinkBuilder when creating a PendingIntent whose destination is an Activity. Here's what I ended up going with:

NotificationCompat.Builder(context, context.getString(R.string.notification_channel_id_new_job))
    ...
    .setContentIntent(
        TaskStackBuilder.create(context).run {
            addNextIntentWithParentStack(Intent(context, DestinationActivity::class.java).apply {
                putExtras(DestinationActivityArgs.Builder(jobId).build().toBundle())
            })
            getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
        }
    )
    .build()

This solution allows me to successfully deep link to a destination Activity while still being able to reference the args defined in the navigation graph for that Activity via the generated DestinationActivityArgs builder, and accessing the args from the destination Activity's onCreate() method works.

This solution also correctly handles the cases when the app task is not in the 'recent apps' list, the app is in the foreground showing some other Activity or Fragment, or the app is in the foreground and already on the destination Activity. addNextIntentWithParentStack() properly handles up navigation, so clicking the up button from the destination Activity navigates back to the logical parent Activity as defined in the AndroidManifest.

It's a slight bummer that this solution isn't directly making use of that Navigation Architecture library to build the PendingIntent, but this feels like the best alternative.

Ryan
  • 3,414
  • 2
  • 27
  • 34
1

You can find all arguments that you send in the arguments variable of your Activity/Fragment.

Here's an example on how to retrieve a String argument set to the PendingIntent created by the NavDeepLinkBuilder() with key EVENT_KEY at your destination Activity/Fragment:

requireArguments().getString(YourClass.EVENT_KEY)?.let { it } ?: ""

Notice that our getString() call may return null in case the value we're looking for isn't there, so do make sure the Bundle you ultimately assign to NavDeepLinkBulder().setArguments() is populated the way you want it!

Ivan Garza
  • 553
  • 2
  • 14
0

I was having the same problem, my bundle was always returning null, this is what worked for me:

  1. On my fragment I have a method that passes the bundle:

    notificationManager.sendNotification(
        requireArguments(), // This gets me the arguments supplied when the fragment in instantiated
        requireContext()
    )
    
  2. On my notificationUtils my content Intent is:

    fun NotificationManager.sendNotification(
        bundle: Bundle,
        appContext: Context
    ) {
        val contentIntent = NavDeepLinkBuilder(appContext)
            .setComponentName(MainActivity::class.java)
            .setGraph(R.navigation.navigation)
            .setDestination(R.id.detailsFragment)
            .setArguments(bundle)
            .createPendingIntent()
    
            // Rest of code to build the notification... 
    }
    
  3. To test if the bundle was being passed correclty I logged as follows:

    Timber.d("The name is: ${bundle.getParcelable<Person>("Person")?.name}")
    
Doilio Matsinhe
  • 2,131
  • 16
  • 29