Visitar URL original
feat(analytics,ios): add UIScene support for campaign attribution (#1… · firebase/flutterfire@5cb3564 · GitHub
Skip to content

Commit 5cb3564

Browse files
authored
feat(analytics,ios): add UIScene support for campaign attribution (#18573)
* feat(analytics,ios): add UIScene support for campaign attribution Forward scene URL and user-activity callbacks to Analytics.handleOpen / handleUserActivity so firebase_campaign is recorded when the host app uses UIScene. * fix(analytics,ios): use lowerCamelCase for openURLContexts parameter * test(analytics,ios): add live e2e coverage for UIScene campaign attribution Drive the scene handlers against the live Analytics SDK so CI exercises handleOpen / handleUserActivity, which BigQuery cannot observe in time. * chore: retrigger CI * fix(analytics,ios): drop redundant FlutterError Error conformance firebase_core already provides the retroactive conformance; redeclaring it fails CI on Flutter SDKs where FlutterError already conforms to Error. * test(analytics,ios): remove UIScene campaign e2e harness The test channel only echoed plugin state and could not observe firebase_campaign. Keep the scene-delegate forwarding, which was verified manually.
1 parent 1283136 commit 5cb3564

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

‎packages/firebase_analytics/firebase_analytics/ios/firebase_analytics/Sources/firebase_analytics/FirebaseAnalyticsPlugin.swift‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import StoreKit
99
import FlutterMacOS
1010
#else
1111
import Flutter
12+
import UIKit
1213
#endif
1314

1415
#if canImport(firebase_core)
@@ -45,6 +46,9 @@ public class FirebaseAnalyticsPlugin: NSObject, FLTFirebasePluginProtocol, Flutt
4546

4647
let instance = FirebaseAnalyticsPlugin()
4748
FirebaseAnalyticsHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: instance)
49+
#if os(iOS)
50+
registerSceneDelegateIfAvailable(registrar, instance: instance)
51+
#endif
4852
}
4953

5054
func logEvent(event: [String: Any?], completion: @escaping (Result<Void, any Error>) -> Void) {
@@ -294,3 +298,73 @@ public class FirebaseAnalyticsPlugin: NSObject, FLTFirebasePluginProtocol, Flutt
294298
FLTFirebaseAnalyticsChannelName
295299
}
296300
}
301+
302+
#if os(iOS)
303+
/// Registers for UIScene callbacks when the host Flutter SDK supports it.
304+
///
305+
/// `addSceneDelegate(_:)` and `FlutterSceneLifeCycleDelegate` were added in
306+
/// Flutter 3.38. Calling via `responds(to:)` keeps this plugin compiling on
307+
/// older Flutter versions; the methods below are unused until registration
308+
/// succeeds.
309+
private func registerSceneDelegateIfAvailable(
310+
_ registrar: FlutterPluginRegistrar,
311+
instance: FirebaseAnalyticsPlugin
312+
) {
313+
let selector = NSSelectorFromString("addSceneDelegate:")
314+
guard (registrar as AnyObject).responds(to: selector) else {
315+
return
316+
}
317+
_ = (registrar as AnyObject).perform(selector, with: instance)
318+
}
319+
320+
/// Forwards UIScene URL and universal-link opens to Analytics.
321+
///
322+
/// The native SDK only swizzles `UIApplicationDelegate`. Under UIScene, UIKit
323+
/// delivers those opens here instead, so campaign attribution (`firebase_campaign`)
324+
/// is lost unless we call `handleOpen` / `handleUserActivity` ourselves.
325+
///
326+
/// Always returns `false` so later plugins (Auth, Messaging, etc.) still receive
327+
/// the same scene events. Selectors match `FlutterSceneLifeCycleDelegate`
328+
/// without conforming to that protocol, which is absent from older Flutter SDKs.
329+
extension FirebaseAnalyticsPlugin {
330+
@objc(scene:willConnectToSession:options:)
331+
public func scene(
332+
_: UIScene,
333+
willConnectTo _: UISceneSession,
334+
options connectionOptions: UIScene.ConnectionOptions?
335+
) -> Bool {
336+
handleCampaign(urlContexts: connectionOptions?.urlContexts)
337+
handleCampaign(userActivities: connectionOptions?.userActivities)
338+
return false
339+
}
340+
341+
@objc(scene:openURLContexts:)
342+
public func scene(
343+
_: UIScene,
344+
openURLContexts urlContexts: Set<UIOpenURLContext>
345+
) -> Bool {
346+
handleCampaign(urlContexts: urlContexts)
347+
return false
348+
}
349+
350+
@objc(scene:continueUserActivity:)
351+
public func scene(_: UIScene, continue userActivity: NSUserActivity) -> Bool {
352+
Analytics.handleUserActivity(userActivity)
353+
return false
354+
}
355+
356+
private func handleCampaign(urlContexts: Set<UIOpenURLContext>?) {
357+
guard let urlContexts else { return }
358+
for context in urlContexts {
359+
Analytics.handleOpen(context.url)
360+
}
361+
}
362+
363+
private func handleCampaign(userActivities: Set<NSUserActivity>?) {
364+
guard let userActivities else { return }
365+
for activity in userActivities {
366+
Analytics.handleUserActivity(activity)
367+
}
368+
}
369+
}
370+
#endif

0 commit comments

Comments
 (0)