6

I'm sending a daily notification to the user based on a condition from the Worker that is launched using BroadcastReceiver. From Worker I only have context for sending notification.

NotificationCompat.Builder(context, CHANNEL_ID).apply {
    setContentTitle(...)
    setContentText(...)
    setContentIntent(pendingIntent(context)) 
}.build()

How do I create PendingIntent for launching a particular fragment using Navigation component?

I tried this:

fun pendingIntent(context: Context): PendingIntent {
    val navController = NavController(context.applicationContext)
    navController.setGraph(R.navigation.your_navigation)
    return navController.createDeepLink()
        .setDestination(R.id.yourFragment)
        .createPendingIntent()
}

But I get following exceptions:

Caused by: java.lang.RuntimeException: Exception inflating package.name:navigation/your_navigation line 7
        at androidx.navigation.NavInflater.inflate(NavInflater.java:90)
        at androidx.navigation.NavController.setGraph(NavController.java:425)
        at androidx.navigation.NavController.setGraph(NavController.java:407)
        at 
        ......
        at package.name.Worker.doWork(Worker.kt:15)
        at androidx.work.Worker$1.run(Worker.java:85)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
        at java.lang.Thread.run(Thread.java:761) 
Caused by: java.lang.IllegalStateException: Could not find Navigator with name "fragment". You must call NavController.addNavigator() for each navigation type.
        at androidx.navigation.NavigatorProvider.getNavigator(NavigatorProvider.java:98)
        at androidx.navigation.NavInflater.inflate(NavInflater.java:100)
        at androidx.navigation.NavInflater.inflate(NavInflater.java:132)
        at androidx.navigation.NavInflater.inflate(NavInflater.java:81)
        at androidx.navigation.NavController.setGraph(NavController.java:425) 
        at androidx.navigation.NavController.setGraph(NavController.java:407) 
        at package.name.NotificationKt.pendingIntent(Notification.kt:57) 
        at package.name.buildNotificationFor(Notification.kt:50) 
        at package.name.showNotificationFor(Notification.kt:22) 
        at package.name.doWork(Worker.kt:15) 
        at androidx.work.Worker$1.run(Worker.java:85) 
Yogesh Umesh Vaity
  • 41,009
  • 21
  • 145
  • 105

2 Answers2

16

As per the Create an explicit deep link documentation, you should be using the NavDeepLinkBuilder class:

fun pendingIntent(context: Context): PendingIntent {
    return NavDeepLinkBuilder(context)
       .setGraph(R.navigation.your_navigation)
       .setDestination(R.id.android)
       .createPendingIntent()
}
ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • 1
    Worked like a charm. Thanks a lot, great to see you answering my question. You have been a huge help here on SO, throughout my learning. – Yogesh Umesh Vaity Nov 19 '19 at 06:24
  • 1
    Thanks for the kind words! Best of luck going forward :) – ianhanniballake Nov 19 '19 at 06:24
  • I have tried to use this code like this https://i.stack.imgur.com/RUFNJ.png , but when I click notification, it will bring me to home, not to inbox destination that I want. the destination_inbox is a menu in my bottom navigation bar like this https://i.stack.imgur.com/MF0UR.png. am I missing something ? :( – Alexa289 Aug 26 '20 at 03:36
  • @Alexa289 - `destination_inbox` needs to be a destination in your navigation graph XML. If deep linking to a destination in your graph isn't working, you could always [file a bug against Navigation](https://issuetracker.google.com/issues/new?component=409828) with a minimal sample project that reproduces your issue. – ianhanniballake Aug 26 '20 at 05:25
  • @ianhanniballake yup, I believe the inbox destination is already in xml graph like this https://i.stack.imgur.com/7EXsb.png , in gradle I am using implementation "androidx.navigation:navigation-fragment-ktx:2.3.0" and implementation "androidx.navigation:navigation-ui-ktx:2.3.0" – Alexa289 Aug 26 '20 at 06:21
  • @ShahrozKhan91 yup, If you have more than one Activity, then you should add .setComponentName(MainActivity::class.java) https://stackoverflow.com/a/55245317/9605341 – Alexa289 Oct 04 '20 at 12:31
  • 1
    Hi, how do I achieve this when the app is running in the background? – Yeldar Nurpeissov Dec 24 '20 at 08:52
  • @Yeldar.N - on Android 10+ devices you [cannot start an activity from the background](https://developer.android.com/guide/components/activities/background-starts). The guidance there of starting a [time-sensitive notification](https://developer.android.com/training/notify-user/time-sensitive) applies to all API levels as the recommended pattern. All of the PendingIntents used there can be generated with `NavDeepLinkBuilder`. – ianhanniballake Dec 24 '20 at 17:09
  • @Alexa289 - check out the answer provided [bellow](https://stackoverflow.com/a/66397131/7680523) – ilatyphi95 Feb 27 '21 at 09:02
  • @Yeldar.N I had the same question. – sprajagopal Jun 18 '21 at 08:54
  • @ianhanniballake I think the bigger issue with this method is that a new instance of the activity is created. Say there is an app running in the foreground and the user clicks on the notification, this would lead to a duplicate activity instead of the existing one. – sprajagopal Jun 18 '21 at 08:56
  • If you have multiple destinations then don't forget to add `setComponentName(DestinationActivity::class.java` – Zeeshan Shabbir Jun 25 '21 at 10:55
2

This answer is more suited for the question asked by @Alexa289 here but since the question was closed on the basis that this is a similar question and answer provided by @ianhanniballeke should suffice. I am compelled to write the answer here. The answer provided by @ianhanniballake is brillant but it only work for certain condition when dealing with onMessageReceived() of a class extending FirebaseMessagingService(). When the activity is in foreground, the service provide activity based context hence the code works fine, but when the activity is not in foreground, the class provide service based context, hence the pendingIntent created from NavDeepLinkBuilder works differently. So here goes my solution for the non-foreground cases. Firstly, create the pendingIntent through the usual way

fun pendingIntent(context: Context): PendingIntent {
    return NavDeepLinkBuilder(context)
       .setGraph(R.navigation.your_navigation)
       .setDestination(R.id.android)
       .createPendingIntent()
}

then you need to catch the pending intent through intent filter in the android manifest like this

        <intent-filter>
            <action android:name="*TheClassNameOfFragmentYourDestinationPointsTo*" />
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>

Then from the home fragment, you can grab the intent that start the activity like this

val intent = (activity as? AppCompatActivity)?.intent

From the intent you can extract the extras in the bundle then navigate to the fragment you wish to like this

findNavController().navigate(R.id.android)
ilatyphi95
  • 555
  • 5
  • 22