Push Notification
App Launch Detection
Mobile Development
In-App Notifications
App Engagement

Detect if the app was launched/opened from a push notification

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Detecting whether an app was opened from a push notification is mostly a question of app state. A cold start, a background resume, and an already running app all enter through different platform callbacks, so a reliable implementation needs to handle those entry points intentionally.

Think in Terms of Launch Paths

The phrase "opened from a push notification" can mean different things:

  • the app was not running and launched because the user tapped a notification
  • the app was in the background and resumed because of a notification tap
  • the app was already running and the notification interaction still triggered navigation

If you only handle one of those paths, the feature works sometimes and fails in ways that are hard to reproduce.

Android: Put Context into the Launch Intent

On Android, the common pattern is to build the notification with a PendingIntent that starts an activity and includes extras marking the navigation as push-driven.

kotlin
1private const val EXTRA_FROM_PUSH = "from_push"
2private const val EXTRA_ORDER_ID = "order_id"
3
4fun buildPendingIntent(context: Context, orderId: String): PendingIntent {
5    val intent = Intent(context, MainActivity::class.java).apply {
6        flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
7        putExtra(EXTRA_FROM_PUSH, true)
8        putExtra(EXTRA_ORDER_ID, orderId)
9    }
10
11    return PendingIntent.getActivity(
12        context,
13        0,
14        intent,
15        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
16    )
17}

Then inspect that intent in both onCreate and onNewIntent, because the activity might be created fresh or reused.

kotlin
1class MainActivity : AppCompatActivity() {
2    override fun onCreate(savedInstanceState: Bundle?) {
3        super.onCreate(savedInstanceState)
4        handleLaunchIntent(intent)
5    }
6
7    override fun onNewIntent(intent: Intent) {
8        super.onNewIntent(intent)
9        setIntent(intent)
10        handleLaunchIntent(intent)
11    }
12
13    private fun handleLaunchIntent(intent: Intent?) {
14        val fromPush = intent?.getBooleanExtra(EXTRA_FROM_PUSH, false) == true
15        val orderId = intent?.getStringExtra(EXTRA_ORDER_ID)
16
17        if (fromPush && orderId != null) {
18            println("Opened from push for order $orderId")
19        }
20    }
21}

That covers both cold start and reuse of an existing activity instance.

iOS: Use the Notification Response Callback

On iOS, a user tap on a notification is delivered through UNUserNotificationCenterDelegate. The response object contains the original payload.

swift
1import UserNotifications
2
3class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
4    func userNotificationCenter(
5        _ center: UNUserNotificationCenter,
6        didReceive response: UNNotificationResponse,
7        withCompletionHandler completionHandler: @escaping () -> Void
8    ) {
9        let userInfo = response.notification.request.content.userInfo
10        let orderId = userInfo["order_id"] as? String
11
12        if let orderId = orderId {
13            print("Opened from push for order \\(orderId)")
14        }
15
16        completionHandler()
17    }
18}

For older app-delegate-style launches, similar payload information may also appear in launch options. The important design choice is the same: convert the raw notification payload into typed app navigation state as early as possible.

Separate Detection from Navigation

Do not scatter push-checking code across multiple screens. A cleaner flow is:

  1. detect the push entry point
  2. normalize the payload into a small internal model
  3. route the app through one navigation function

That keeps notification handling testable and avoids duplicate logic.

kotlin
1data class PushOpen(val orderId: String)
2
3fun parsePushOpen(intent: Intent?): PushOpen? {
4    if (intent?.getBooleanExtra(EXTRA_FROM_PUSH, false) != true) return null
5    val orderId = intent.getStringExtra(EXTRA_ORDER_ID) ?: return null
6    return PushOpen(orderId)
7}

The same principle applies on iOS. Raw dictionaries are a transport format, not the ideal shape for application logic.

Do Not Confuse Delivery with Open

Receiving a push while the app is already in the foreground is not the same as the user opening the app from a notification. Those are separate events and should be tracked separately in analytics and behavior.

If you merge them, notification reports become misleading and UI routing logic becomes harder to reason about.

Common Pitfalls

  • Handling only cold start and forgetting resumed or reused activity cases.
  • Treating message receipt as proof that the user opened the app from the notification.
  • Parsing raw payloads in many places instead of normalizing them once.
  • Forgetting to replace stale intent data when Android reuses an activity instance.
  • Mixing analytics tracking and navigation until both become fragile.

Summary

  • Push-open detection depends on the app state and entry point.
  • On Android, pass extras in the launch intent and handle both onCreate and onNewIntent.
  • On iOS, handle notification taps through the notification response callback.
  • Normalize payloads into app state before navigating.
  • Keep notification delivery and notification open as separate concepts.

Course illustration
Course illustration

All Rights Reserved.