Skip to content

Commit 5d73bd8

Browse files
authored
feat(in_app_messaging): expose the message lifecycle events
Adds `onMessageClicked`, `onMessageImpression`, `onMessageDismissed` and `onMessageDisplayError` to `FirebaseInAppMessaging`, so apps can react to what happens to a campaign message - most importantly read the action URL of the button a user tapped, together with the campaign metadata. The native listeners are attached the first time one of the streams is read, so apps that never listen keep the current behavior and, on iOS, keep ownership of the `InAppMessaging` display delegate. Android reports no dismiss type, so `InAppMessagingDismissType.unknown` is always used there. Closes #12984
1 parent 128912e commit 5d73bd8

17 files changed

Lines changed: 2166 additions & 4 deletions

File tree

‎packages/firebase_in_app_messaging/firebase_in_app_messaging/README.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ To get started with Firebase In-App Messaging for Flutter, please [see the docum
1414

1515
To use this plugin, please visit the [Firebase In-App Messaging Usage documentation](https://firebase.google.com/docs/in-app-messaging/get-started?platform=flutter)
1616

17+
### Message lifecycle events
18+
19+
Listen to the streams below to react to what happens to the messages of your
20+
campaigns, for example to handle the URL of the button a user tapped yourself:
21+
22+
```dart
23+
FirebaseInAppMessaging.instance.onMessageClicked.listen((event) {
24+
print('${event.campaignMetadata.campaignName} -> ${event.action.actionUrl}');
25+
});
26+
```
27+
28+
`onMessageImpression`, `onMessageDismissed` and `onMessageDisplayError` report
29+
the rest of the message lifecycle.
30+
1731
## Issues and feedback
1832

1933
Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/firebase/flutterfire/issues/new).

‎packages/firebase_in_app_messaging/firebase_in_app_messaging/android/src/main/kotlin/io/flutter/plugins/firebase/inappmessaging/FirebaseInAppMessagingPlugin.kt‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,61 @@
33
// found in the LICENSE file.
44
package io.flutter.plugins.firebase.inappmessaging
55

6+
import android.os.Handler
7+
import android.os.Looper
68
import com.google.android.gms.tasks.Task
79
import com.google.android.gms.tasks.TaskCompletionSource
810
import com.google.firebase.FirebaseApp
911
import com.google.firebase.inappmessaging.FirebaseInAppMessaging
12+
import com.google.firebase.inappmessaging.FirebaseInAppMessagingClickListener
13+
import com.google.firebase.inappmessaging.FirebaseInAppMessagingDismissListener
14+
import com.google.firebase.inappmessaging.FirebaseInAppMessagingDisplayErrorListener
15+
import com.google.firebase.inappmessaging.FirebaseInAppMessagingImpressionListener
16+
import com.google.firebase.inappmessaging.model.InAppMessage
1017
import io.flutter.embedding.engine.plugins.FlutterPlugin
1118
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
1219
import io.flutter.plugin.common.BinaryMessenger
1320
import io.flutter.plugins.firebase.core.FlutterFirebasePlugin
1421
import io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry
22+
import java.util.concurrent.Executor
1523

1624
/** FirebaseInAppMessagingPlugin */
1725
class FirebaseInAppMessagingPlugin :
1826
FlutterFirebasePlugin, FlutterPlugin, FirebaseInAppMessagingHostApi {
1927
private var binaryMessenger: BinaryMessenger? = null
28+
private var flutterApi: FirebaseInAppMessagingFlutterApi? = null
29+
30+
// The listeners below forward events to Dart, which has to happen on the main
31+
// thread, so they are registered with a main thread executor.
32+
private val mainThreadExecutor = Executor { command ->
33+
Handler(Looper.getMainLooper()).post(command)
34+
}
35+
36+
private val clickListener = FirebaseInAppMessagingClickListener { inAppMessage, action ->
37+
flutterApi?.onMessageClicked(
38+
campaignMetadata(inAppMessage), FiamAction(action.actionUrl, action.button?.text?.text)) {}
39+
}
40+
41+
private val impressionListener = FirebaseInAppMessagingImpressionListener { inAppMessage ->
42+
flutterApi?.onMessageImpression(campaignMetadata(inAppMessage)) {}
43+
}
44+
45+
private val dismissListener = FirebaseInAppMessagingDismissListener { inAppMessage ->
46+
// The Android SDK does not report how a message was dismissed.
47+
flutterApi?.onMessageDismissed(campaignMetadata(inAppMessage), FiamDismissType.UNKNOWN) {}
48+
}
49+
50+
private val displayErrorListener =
51+
FirebaseInAppMessagingDisplayErrorListener { inAppMessage, errorReason ->
52+
flutterApi?.onMessageDisplayError(campaignMetadata(inAppMessage), errorReason.name) {}
53+
}
54+
55+
private var eventListenersAdded = false
2056

2157
private fun initInstance(messenger: BinaryMessenger) {
2258
FlutterFirebasePluginRegistry.registerPlugin(METHOD_CHANNEL_NAME, this)
2359
binaryMessenger = messenger
60+
flutterApi = FirebaseInAppMessagingFlutterApi(messenger)
2461
FirebaseInAppMessagingHostApi.setUp(messenger, this)
2562
}
2663

@@ -29,10 +66,49 @@ class FirebaseInAppMessagingPlugin :
2966
}
3067

3168
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
69+
removeEventListeners()
3270
binaryMessenger = null
71+
flutterApi = null
3372
FirebaseInAppMessagingHostApi.setUp(binding.binaryMessenger, null)
3473
}
3574

75+
private fun campaignMetadata(inAppMessage: InAppMessage): FiamCampaignMetadata {
76+
val metadata = inAppMessage.campaignMetadata
77+
return FiamCampaignMetadata(
78+
metadata?.campaignId ?: "", metadata?.campaignName ?: "", metadata?.isTestMessage ?: false)
79+
}
80+
81+
private fun removeEventListeners() {
82+
if (!eventListenersAdded) return
83+
eventListenersAdded = false
84+
85+
FirebaseInAppMessaging.getInstance().apply {
86+
removeClickListener(clickListener)
87+
removeImpressionListener(impressionListener)
88+
removeDismissListener(dismissListener)
89+
removeDisplayErrorListener(displayErrorListener)
90+
}
91+
}
92+
93+
override fun addEventListeners(appName: String, callback: (Result<Unit>) -> Unit) {
94+
FlutterFirebasePlugin.cachedThreadPool.execute {
95+
try {
96+
if (!eventListenersAdded) {
97+
FirebaseInAppMessaging.getInstance().apply {
98+
addClickListener(clickListener, mainThreadExecutor)
99+
addImpressionListener(impressionListener, mainThreadExecutor)
100+
addDismissListener(dismissListener, mainThreadExecutor)
101+
addDisplayErrorListener(displayErrorListener, mainThreadExecutor)
102+
}
103+
eventListenersAdded = true
104+
}
105+
callback(Result.success(Unit))
106+
} catch (exception: Exception) {
107+
handleFailure(callback, exception)
108+
}
109+
}
110+
}
111+
36112
override fun triggerEvent(appName: String, eventName: String, callback: (Result<Unit>) -> Unit) {
37113
FlutterFirebasePlugin.cachedThreadPool.execute {
38114
try {

0 commit comments

Comments
 (0)