Skip to content

[firebase_analytics]: campaign attribution (firebase_campaign) silently stops working on iOS under the UIScene lifecycle #18572

Description

@MDemetrio

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Analytics

Which platforms are affected?

iOS

Description

After migrating the iOS host app to the UIScene lifecycle, firebase_analytics stops
recording the firebase_campaign event when the app is opened from a deep link or universal
link. Campaign attribution is silently lost, while every other Analytics event continues to work
normally.

Expected: opening the app from a link carrying campaign parameters (e.g.
?utm_source=...&utm_medium=...&utm_campaign=...) records firebase_campaign, as it did before
the UIScene migration.

Actual: firebase_campaign is never recorded — neither on cold start nor when the app is
resumed from background. There is no error, no warning and no log output of any kind; the deep
links themselves keep working, so only the attribution data disappears.

Reverting the UIScene adoption (removing UIApplicationSceneManifest from Info.plist) restores
the event.

This makes the failure particularly costly: nothing surfaces at runtime, and because
firebase_campaign is only observable in BigQuery (see below), the loss can go unnoticed for a
long time after release. With Apple moving toward mandatory UIScene adoption, this will affect a
growing number of Flutter apps.

Reproducing the issue

  1. Use the firebase_analytics example app (or any Flutter app with firebase_analytics) on
    Flutter > 3.44.0.
  2. Adopt the UIScene lifecycle in the iOS host app — add UIApplicationSceneManifest to
    Info.plist and a SceneDelegate: FlutterSceneDelegate, per Flutter's
    UISceneDelegate migration guide.
  3. Open the app from a link carrying campaign parameters — once on cold start and once with
    the app in background:
    xcrun simctl openurl booted "https://<your-universal-link-domain>/?utm_source=test&utm_medium=test&utm_campaign=uiscene"
    
  4. Verify in the BigQuery export:
    SELECT
      event_timestamp,
      event_name,
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'source')   AS source,
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'medium')   AS medium,
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'campaign') AS campaign
    FROM `<project>.analytics_<property_id>.events_*`
    WHERE event_name = 'firebase_campaign'
      AND _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 3 DAY))
    ORDER BY event_timestamp DESC
  5. Result: no firebase_campaign rows for the UIScene build. The same test on a build
    without UIApplicationSceneManifest produces the event as expected.

⚠️ Note on verification: firebase_campaign is not surfaced in Analytics DebugView
by design — it only appears in the BigQuery export. DebugView cannot be used to confirm or
deny this bug. Mind the BQ export latency when reproducing (daily batch, unless streaming
export is enabled).

Firebase Core version

4.0.0

Flutter Version

3.44.0

Relevant Log Output

No log output is produced — this is part of the problem.
Analytics emits no warning or error; all other events continue to be recorded,
and only firebase_campaign is missing from the BigQuery export.

Flutter dependencies

Expand Flutter dependencies snippet
# Trimmed to the relevant packages (host app is a large monorepo).
firebase_core: 4.0.0
firebase_core_platform_interface: 6.0.0
firebase_analytics: 12.0.0
firebase_analytics_platform_interface: 5.0.0

# iOS native (Podfile.lock)
FirebaseAnalytics: 12.0.0
FirebaseCore: 12.0.0
GoogleUtilities: ~> 8.1

Additional context and comments

I've traced it to how Analytics hooks into the iOS app lifecycle in order to find the root cause and try a workaround, let me know if this is correct:

Firebase Analytics records firebase_campaign by calling FIRAnalytics.handleOpen(_:)
automatically, via swizzling of the UIApplicationDelegate — and only that. It does not
swizzle UISceneDelegate.

Verified against the installed binary (FirebaseAnalytics / GoogleAppMeasurement 12.0.0):

$ nm -u GoogleAppMeasurement.framework/GoogleAppMeasurement | grep -i GUL
_OBJC_CLASS_$_GULAppDelegateSwizzler      # ← linked
_OBJC_CLASS_$_GULAppEnvironmentUtil
_OBJC_CLASS_$_GULSwizzler
# _OBJC_CLASS_$_GULSceneDelegateSwizzler  → NOT linked

The only lifecycle selectors it references are UIApplicationDelegate ones — no
scene:openURLContexts::

$ strings GoogleAppMeasurement | grep -iE "continueUserActivity|handleOpenURL|openURLContexts"
application:continueUserActivity:restorationHandler:
application:handleOpenURL:
handleOpenURL:clickTimestampInMs:eventTimestampInSeconds:

Under UIScene, UIKit delivers URL opens to scene(_:openURLContexts:), scene(_:continue:) and
scene(_:willConnectTo:options:), and never calls the AppDelegate equivalents — so the
swizzled implementation is never reached and handleOpen(_:) is never invoked.

This is documented native behaviour, not a native bug —
FIRAnalytics(AppDelegate):

Analytics does not require delegation implementation from the AppDelegate if neither
SwiftUI nor UIScene lifecycle is adopted.

…and it specifies the manual calls required under UIScene:

UIScene callback Required call
scene(_:willConnectTo:options:) handleOpen(_:) and handleUserActivity(_:)
scene(_:openURLContexts:) handleOpen(_:)
scene(_:continue:) handleUserActivity(_:)

The firebase_analytics iOS plugin never calls handleOpen/handleUserActivity — it relies
entirely on the native SDK's AppDelegate swizzling, which UIScene makes unreachable. There is
no scene-lifecycle handling in the plugin at all:

$ grep -rn "addSceneDelegate\|FlutterSceneLifeCycleDelegate\|openURLContexts\|handleOpen" \
    firebase_analytics-12.0.0/ios/
# (no matches)

Since Flutter 3.38, plugins can register a scene delegate via registrar.addSceneDelegate(_:)
(FlutterPlugin.h) implementing FlutterSceneLifeCycleDelegate — the mechanism described in
Flutter's
UISceneDelegate plugin migration guide.

Related UIScene gaps already addressed in other plugins: #18054 / #18383 (Auth), #18555
(Messaging). Analytics appears to have been missed.

Notes

  • GoogleUtilities does ship a GULSceneDelegateSwizzler, but Analytics doesn't link it — and
    it only covers scene:openURLContexts:, not scene:continue: (universal links) or
    willConnectTo (cold start), so enabling it alone would not be sufficient.
  • The failure is easy to miss: deep links keep working (the plugin ecosystem handles scene
    events), so only campaign telemetry breaks — with no diagnostic, and no visibility in
    DebugView since firebase_campaign is BigQuery-only.

Metadata

Metadata

Assignees

Labels

blocked: customer-responseWaiting for customer response, e.g. more information was requested.platform: iosIssues / PRs which are specifically for iOS.plugin: analyticstype: bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions