From 0ed0975c82808003d4202608929bfc24d987de3d Mon Sep 17 00:00:00 2001 From: Allie Date: Tue, 17 Jun 2025 15:17:50 -0400 Subject: [PATCH 1/6] add stream listener wrapper for windowDidHide event --- intercom_flutter/README.md | 129 +++++++++++++----- .../maido/intercom/IntercomFlutterPlugin.kt | 24 +++- .../example/ios/Runner/Info.plist | 2 + intercom_flutter/example/lib/main.dart | 71 ++++++++-- .../ios/Classes/IntercomFlutterPlugin.h | 3 + .../ios/Classes/IntercomFlutterPlugin.m | 21 +++ intercom_flutter/lib/intercom_flutter.dart | 9 ++ .../test/intercom_flutter_test.dart | 28 ++++ .../intercom_flutter_platform_interface.dart | 10 ++ .../lib/method_channel_intercom_flutter.dart | 7 + 10 files changed, 253 insertions(+), 51 deletions(-) diff --git a/intercom_flutter/README.md b/intercom_flutter/README.md index 5c3e6397..f931b145 100755 --- a/intercom_flutter/README.md +++ b/intercom_flutter/README.md @@ -17,6 +17,7 @@ Flutter wrapper for Intercom [Android](https://github.com/intercom/intercom-andr Import `package:intercom_flutter/intercom_flutter.dart` and use the methods in `Intercom` class. Example: + ```dart import 'package:flutter/material.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; @@ -50,6 +51,53 @@ class App extends StatelessWidget { See Intercom [Android](https://developers.intercom.com/installing-intercom/docs/intercom-for-android) and [iOS](https://developers.intercom.com/installing-intercom/docs/intercom-for-ios) package documentation for more information. +### Listening for Intercom Window Events + +You can listen for when the Intercom window is hidden (closed) using the `getWindowDidHideStream()` method. This is particularly useful for performing actions when users close the Intercom messenger, help center, or other Intercom windows. + +**Note:** This feature is only available on iOS. + +````dart +import 'package:flutter/material.dart'; +import 'package:intercom_flutter/intercom_flutter.dart'; +import 'dart:async'; + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + StreamSubscription? _windowDidHideSubscription; + + @override + void initState() { + super.initState(); + // Listen for when the Intercom window is hidden + _windowDidHideSubscription = Intercom.instance.getWindowDidHideStream().listen((_) { + // This will be called when the Intercom window is closed + print('Intercom window was closed!'); + // Perform any actions you need when the window is closed + }); + } + + @override + void dispose() { + _windowDidHideSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FlatButton( + child: Text('Open Intercom'), + onPressed: () async { + await Intercom.instance.displayMessenger(); + }, + ); + } +} + ### Android Make sure that your app's `MainActivity` extends `FlutterFragmentActivity` (you can check the example). @@ -57,7 +105,7 @@ Make sure that your app's `MainActivity` extends `FlutterFragmentActivity` (you Permissions: ```xml -``` +```` Optional permissions: @@ -75,25 +123,32 @@ android.enableJetifier=true ``` According to the documentation, Intercom must be initialized in the Application onCreate. So follow the below steps to achieve the same: + - Setup custom application class if you don't have any. - - Create a custom `android.app.Application` class named `MyApp`. - - Add an `onCreate()` override. The class should look like this: - ```kotlin - import android.app.Application - class MyApp: Application() { + - Create a custom `android.app.Application` class named `MyApp`. + - Add an `onCreate()` override. The class should look like this: + + ```kotlin + import android.app.Application + + class MyApp: Application() { + + override fun onCreate() { + super.onCreate() + } + } + ``` + + - Open your `AndroidManifest.xml` and find the `application` tag. In it, add an `android:name` attribute, and set the value to your class' name, prefixed by a dot (.). + + ```xml + + ``` - override fun onCreate() { - super.onCreate() - } - } - ``` - - Open your `AndroidManifest.xml` and find the `application` tag. In it, add an `android:name` attribute, and set the value to your class' name, prefixed by a dot (.). - ```xml - - ``` - Now initialize the Intercom SDK inside the `onCreate()` of custom application class according to the following: + ```kotlin import android.app.Application import io.maido.intercom.IntercomFlutterPlugin @@ -109,16 +164,18 @@ class MyApp : Application() { ``` ### iOS + Make sure that you have a `NSPhotoLibraryUsageDescription` entry in your `Info.plist`. ### Push notifications setup + This plugin works in combination with the [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) plugin to receive Push Notifications. To set this up: -* First, implement [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) -* Then, add the Firebase server key to Intercom, as described [here](https://developers.intercom.com/installing-intercom/docs/android-fcm-push-notifications#section-step-3-add-your-server-key-to-intercom-for-android-settings) (you can skip 1 and 2 as you have probably done them while configuring `firebase_messaging`) -* Follow the steps as described [here](https://developers.intercom.com/installing-intercom/docs/ios-push-notifications) to enable push notification in iOS. -* Starting from Android 13 you may need to ask for notification permissions (as of version 13 `firebase_messaging` should support that) -* Ask FirebaseMessaging for the token that we need to send to Intercom, and give it to Intercom (so Intercom can send push messages to the correct device), please note that in order to receive push notifications in your iOS app, you have to send the APNS token to Intercom. The example below uses [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) to get either the FCM or APNS token based on the platform: +- First, implement [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) +- Then, add the Firebase server key to Intercom, as described [here](https://developers.intercom.com/installing-intercom/docs/android-fcm-push-notifications#section-step-3-add-your-server-key-to-intercom-for-android-settings) (you can skip 1 and 2 as you have probably done them while configuring `firebase_messaging`) +- Follow the steps as described [here](https://developers.intercom.com/installing-intercom/docs/ios-push-notifications) to enable push notification in iOS. +- Starting from Android 13 you may need to ask for notification permissions (as of version 13 `firebase_messaging` should support that) +- Ask FirebaseMessaging for the token that we need to send to Intercom, and give it to Intercom (so Intercom can send push messages to the correct device), please note that in order to receive push notifications in your iOS app, you have to send the APNS token to Intercom. The example below uses [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) to get either the FCM or APNS token based on the platform: ```dart final firebaseMessaging = FirebaseMessaging.instance; @@ -130,15 +187,18 @@ Intercom.instance.sendTokenToIntercom(intercomToken); Now, if either Firebase direct (e.g. by your own backend server) or Intercom sends you a message, it will be delivered to your app. ### Web + You don't need to do any extra steps for the web. Intercom script will be automatically injected. But you can pre-define some Intercom settings, if you want (optional). + ```html ``` + #### Following functions are not yet supported on Web: - [ ] unreadConversationCount @@ -149,18 +209,22 @@ But you can pre-define some Intercom settings, if you want (optional). - [ ] handlePush - [ ] displayCarousel - [ ] displayHelpCenterCollections +- [ ] getWindowDidHideStream ## Using Intercom keys with `--dart-define` -Use `--dart-define` variables to avoid hardcoding Intercom keys. +Use `--dart-define` variables to avoid hardcoding Intercom keys. ### Pass the Intercom keys with `flutter run` or `flutter build` command using `--dart-define`. + ```dart flutter run --dart-define="INTERCOM_APP_ID=appID" --dart-define="INTERCOM_ANDROID_KEY=androidKey" --dart-define="INTERCOM_IOS_KEY=iosKey" ``` + Note: You can also use `--dart-define-from-file` which is introduced in Flutter 3.7. ### Reading keys in Dart side and initialize the SDK. + ```dart String appId = String.fromEnvironment("INTERCOM_APP_ID", ""); String androidKey = String.fromEnvironment("INTERCOM_ANDROID_KEY", ""); @@ -171,7 +235,8 @@ Intercom.instance.initialize(appId, iosApiKey: iOSKey, androidApiKey: androidKey ### Reading keys in Android native side and initialize the SDK. -* Add the following code to `build.gradle`. +- Add the following code to `build.gradle`. + ``` def dartEnvironmentVariables = [] if (project.hasProperty('dart-defines')) { @@ -184,7 +249,8 @@ if (project.hasProperty('dart-defines')) { } ``` -* Place `dartEnvironmentVariables` inside the build config +- Place `dartEnvironmentVariables` inside the build config + ``` defaultConfig { ... @@ -193,7 +259,8 @@ defaultConfig { } ``` -* Read the build config fields +- Read the build config fields + ```kotlin import android.app.Application import android.os.Build @@ -202,10 +269,10 @@ import io.maido.intercom.IntercomFlutterPlugin class MyApp : Application() { override fun onCreate() { super.onCreate() - + // Add this line with your keys - IntercomFlutterPlugin.initSdk(this, - appId = BuildConfig.INTERCOM_APP_ID, + IntercomFlutterPlugin.initSdk(this, + appId = BuildConfig.INTERCOM_APP_ID, androidApiKey = BuildConfig.INTERCOM_ANDROID_KEY) } } diff --git a/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt b/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt index c952c17e..8e8b6323 100644 --- a/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt +++ b/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt @@ -32,6 +32,8 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str channel.setMethodCallHandler(IntercomFlutterPlugin()) val unreadEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "maido.io/intercom/unread") unreadEventChannel.setStreamHandler(IntercomFlutterPlugin()) + val windowDidHideEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "maido.io/intercom/windowDidHide") + windowDidHideEventChannel.setStreamHandler(IntercomFlutterPlugin()) application = flutterPluginBinding.applicationContext as Application } @@ -366,14 +368,22 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str } override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - unreadConversationCountListener = UnreadConversationCountListener { count -> - val handler = android.os.Handler(android.os.Looper.getMainLooper()) - handler.post { - events?.success(count) + // Check if this is the unread stream or windowDidHide stream + // For unread stream, we set up the listener + // For windowDidHide stream, we don't set up anything since it's iOS-specific + if (arguments == null || arguments.toString().contains("unread") || arguments.toString().isEmpty()) { + // This is the unread stream + unreadConversationCountListener = UnreadConversationCountListener { count -> + val handler = android.os.Handler(android.os.Looper.getMainLooper()) + handler.post { + events?.success(count) + } + }.also { + Intercom.client().addUnreadConversationCountListener(it) } - }.also { - Intercom.client().addUnreadConversationCountListener(it) - } + } + // For windowDidHide stream, we don't need to do anything since it's iOS-specific + // The stream will not emit any events on Android } override fun onCancel(arguments: Any?) { diff --git a/intercom_flutter/example/ios/Runner/Info.plist b/intercom_flutter/example/ios/Runner/Info.plist index abc92b33..37956cfb 100644 --- a/intercom_flutter/example/ios/Runner/Info.plist +++ b/intercom_flutter/example/ios/Runner/Info.plist @@ -43,5 +43,7 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/intercom_flutter/example/lib/main.dart b/intercom_flutter/example/lib/main.dart index 69ac660f..e2a0e454 100644 --- a/intercom_flutter/example/lib/main.dart +++ b/intercom_flutter/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; @@ -13,7 +15,39 @@ void main() async { runApp(SampleApp()); } -class SampleApp extends StatelessWidget { +class SampleApp extends StatefulWidget { + @override + _SampleAppState createState() => _SampleAppState(); +} + +class _SampleAppState extends State { + StreamSubscription? _windowDidHideSubscription; + + @override + void initState() { + super.initState(); + // Listen for when the Intercom window is hidden + _windowDidHideSubscription = + Intercom.instance.getWindowDidHideStream().listen((_) { + // This will be called when the Intercom window is closed + // Only works on iOS + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Intercom window was closed!'), + duration: Duration(seconds: 2), + ), + ); + } + }); + } + + @override + void dispose() { + _windowDidHideSubscription?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -22,18 +56,29 @@ class SampleApp extends StatelessWidget { title: const Text('Intercom example app'), ), body: Center( - child: TextButton( - onPressed: () { - // NOTE: - // Messenger will load the messages only if the user is registered - // in Intercom. - // Either identified or unidentified. - // So make sure to login the user in Intercom first before opening - // the intercom messenger. - // Otherwise messenger will not load. - Intercom.instance.displayMessenger(); - }, - child: Text('Show messenger'), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () { + // NOTE: + // Messenger will load the messages only if the user is registered + // in Intercom. + // Either identified or unidentified. + // So make sure to login the user in Intercom first before opening + // the intercom messenger. + // Otherwise messenger will not load. + Intercom.instance.displayMessenger(); + }, + child: Text('Show messenger'), + ), + SizedBox(height: 20), + Text( + 'Close the Intercom window to see the notification!', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ], ), ), ), diff --git a/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h b/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h index 67677316..f6dd6067 100644 --- a/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h +++ b/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h @@ -5,3 +5,6 @@ @interface UnreadStreamHandler : NSObject @end + +@interface WindowDidHideStreamHandler : NSObject +@end diff --git a/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m b/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m index a77eea3d..2ea3f34c 100644 --- a/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m +++ b/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m @@ -2,6 +2,7 @@ #import id unread; +id windowDidHide; @implementation UnreadStreamHandler - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { @@ -18,6 +19,20 @@ - (FlutterError*)onCancelWithArguments:(id)arguments { } @end +@implementation WindowDidHideStreamHandler +- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { + windowDidHide = [[NSNotificationCenter defaultCenter] addObserverForName:IntercomWindowDidHideNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { + eventSink(@(YES)); + }]; + return nil; +} + +- (FlutterError*)onCancelWithArguments:(id)arguments { + [[NSNotificationCenter defaultCenter] removeObserver:windowDidHide]; + return nil; +} +@end + @implementation IntercomFlutterPlugin + (void)registerWithRegistrar:(NSObject*)registrar { IntercomFlutterPlugin* instance = [[IntercomFlutterPlugin alloc] init]; @@ -31,6 +46,12 @@ + (void)registerWithRegistrar:(NSObject*)registrar { [[UnreadStreamHandler alloc] init]; [unreadChannel setStreamHandler:unreadStreamHandler]; + FlutterEventChannel* windowDidHideChannel = [FlutterEventChannel eventChannelWithName:@"maido.io/intercom/windowDidHide" + binaryMessenger:[registrar messenger]]; + WindowDidHideStreamHandler* windowDidHideStreamHandler = + [[WindowDidHideStreamHandler alloc] init]; + [windowDidHideChannel setStreamHandler:windowDidHideStreamHandler]; + } - (void) handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{ diff --git a/intercom_flutter/lib/intercom_flutter.dart b/intercom_flutter/lib/intercom_flutter.dart index b4b794bf..859472f0 100755 --- a/intercom_flutter/lib/intercom_flutter.dart +++ b/intercom_flutter/lib/intercom_flutter.dart @@ -48,6 +48,15 @@ class Intercom { return IntercomFlutterPlatform.instance.getUnreadStream(); } + /// You can listen for when the Intercom window is hidden. + /// + /// This stream emits when the Intercom window (messenger, help center, etc.) is closed. + /// This allows developers to perform certain actions in their app when the Intercom window is closed. + /// Only available on iOS. + Stream getWindowDidHideStream() { + return IntercomFlutterPlatform.instance.getWindowDidHideStream(); + } + /// This method allows you to set a fixed bottom padding for in app messages and the launcher. /// /// It is useful if your app has a tab bar or similar UI at the bottom of your window. diff --git a/intercom_flutter/test/intercom_flutter_test.dart b/intercom_flutter/test/intercom_flutter_test.dart index 5239c267..e9090683 100755 --- a/intercom_flutter/test/intercom_flutter_test.dart +++ b/intercom_flutter/test/intercom_flutter_test.dart @@ -242,6 +242,34 @@ void main() { }); }); + group('WindowDidHide', () { + const String channelName = 'maido.io/intercom/windowDidHide'; + const MethodChannel channel = MethodChannel(channelName); + final bool value = true; + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + channelName, + const StandardMethodCodec().encodeSuccessEnvelope(value), + (ByteData? data) {}, + ); + return; + }); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('testStream', () async { + expect(await Intercom.instance.getWindowDidHideStream().first, value); + }); + }); + test('displayArticle', () async { final String testArticleId = "123456"; await Intercom.instance.displayArticle(testArticleId); diff --git a/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart b/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart index 4f5b7494..f90b53c8 100644 --- a/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart +++ b/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart @@ -47,6 +47,16 @@ abstract class IntercomFlutterPlatform extends PlatformInterface { throw UnimplementedError('getUnreadStream() has not been implemented.'); } + /// You can listen for when the Intercom window is hidden. + /// + /// This stream emits when the Intercom window (messenger, help center, etc.) is closed. + /// This allows developers to perform certain actions in their app when the Intercom window is closed. + /// Only available on iOS. + Stream getWindowDidHideStream() { + throw UnimplementedError( + 'getWindowDidHideStream() has not been implemented.'); + } + /// To make sure that conversations between you and your users are kept private /// and that one user can't impersonate another then you need you need to setup /// the identity verification. diff --git a/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart b/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart index 949a8c50..c511a84c 100644 --- a/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart +++ b/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart @@ -4,6 +4,8 @@ import 'package:intercom_flutter_platform_interface/intercom_status_callback.dar const MethodChannel _channel = MethodChannel('maido.io/intercom'); const EventChannel _unreadChannel = EventChannel('maido.io/intercom/unread'); +const EventChannel _windowDidHideChannel = + EventChannel('maido.io/intercom/windowDidHide'); /// An implementation of [IntercomFlutterPlatform] that uses method channels. class MethodChannelIntercomFlutter extends IntercomFlutterPlatform { @@ -25,6 +27,11 @@ class MethodChannelIntercomFlutter extends IntercomFlutterPlatform { return _unreadChannel.receiveBroadcastStream(); } + @override + Stream getWindowDidHideStream() { + return _windowDidHideChannel.receiveBroadcastStream(); + } + @override Future setUserHash(String userHash) async { await _channel.invokeMethod('setUserHash', {'userHash': userHash}); From c9f5356a39f32b8d05afa3342ba4aa443cf9335f Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 6 Apr 2026 13:12:43 +0530 Subject: [PATCH 2/6] Remove unwanted changes --- intercom_flutter/README.md | 87 +++++++++++++++----------------------- 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/intercom_flutter/README.md b/intercom_flutter/README.md index 1e281efc..c58d3559 100755 --- a/intercom_flutter/README.md +++ b/intercom_flutter/README.md @@ -17,7 +17,6 @@ Flutter wrapper for Intercom [Android](https://github.com/intercom/intercom-andr Import `package:intercom_flutter/intercom_flutter.dart` and use the methods in `Intercom` class. Example: - ```dart import 'package:flutter/material.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; @@ -57,7 +56,7 @@ You can listen for when the Intercom window is hidden (closed) using the `getWin **Note:** This feature is only available on iOS. -````dart +```dart import 'package:flutter/material.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; import 'dart:async'; @@ -73,11 +72,8 @@ class _MyAppState extends State { @override void initState() { super.initState(); - // Listen for when the Intercom window is hidden _windowDidHideSubscription = Intercom.instance.getWindowDidHideStream().listen((_) { - // This will be called when the Intercom window is closed print('Intercom window was closed!'); - // Perform any actions you need when the window is closed }); } @@ -97,6 +93,7 @@ class _MyAppState extends State { ); } } +``` ### Android @@ -105,7 +102,7 @@ Make sure that your app's `MainActivity` extends `FlutterFragmentActivity` (you Permissions: ```xml -```` +``` Optional permissions: @@ -123,32 +120,25 @@ android.enableJetifier=true ``` According to the documentation, Intercom must be initialized in the Application onCreate. So follow the below steps to achieve the same: - - Setup custom application class if you don't have any. + - Create a custom `android.app.Application` class named `MyApp`. + - Add an `onCreate()` override. The class should look like this: + ```kotlin + import android.app.Application - - Create a custom `android.app.Application` class named `MyApp`. - - Add an `onCreate()` override. The class should look like this: - - ```kotlin - import android.app.Application - - class MyApp: Application() { - - override fun onCreate() { - super.onCreate() - } - } - ``` - - - Open your `AndroidManifest.xml` and find the `application` tag. In it, add an `android:name` attribute, and set the value to your class' name, prefixed by a dot (.). - - ```xml - - ``` + class MyApp: Application() { + override fun onCreate() { + super.onCreate() + } + } + ``` + - Open your `AndroidManifest.xml` and find the `application` tag. In it, add an `android:name` attribute, and set the value to your class' name, prefixed by a dot (.). + ```xml + + ``` - Now initialize the Intercom SDK inside the `onCreate()` of custom application class according to the following: - ```kotlin import android.app.Application import io.maido.intercom.IntercomFlutterPlugin @@ -164,18 +154,16 @@ class MyApp : Application() { ``` ### iOS - Make sure that you have a `NSPhotoLibraryUsageDescription` entry in your `Info.plist`. ### Push notifications setup - This plugin works in combination with the [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) plugin to receive Push Notifications. To set this up: -- First, implement [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) -- Then, add the Firebase server key to Intercom, as described [here](https://developers.intercom.com/installing-intercom/docs/android-fcm-push-notifications#section-step-3-add-your-server-key-to-intercom-for-android-settings) (you can skip 1 and 2 as you have probably done them while configuring `firebase_messaging`) -- Follow the steps as described [here](https://developers.intercom.com/installing-intercom/docs/ios-push-notifications) to enable push notification in iOS. -- Starting from Android 13 you may need to ask for notification permissions (as of version 13 `firebase_messaging` should support that) -- Ask FirebaseMessaging for the token that we need to send to Intercom, and give it to Intercom (so Intercom can send push messages to the correct device), please note that in order to receive push notifications in your iOS app, you have to send the APNS token to Intercom. The example below uses [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) to get either the FCM or APNS token based on the platform: +* First, implement [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) +* Then, add the Firebase server key to Intercom, as described [here](https://developers.intercom.com/installing-intercom/docs/android-fcm-push-notifications#section-step-3-add-your-server-key-to-intercom-for-android-settings) (you can skip 1 and 2 as you have probably done them while configuring `firebase_messaging`) +* Follow the steps as described [here](https://developers.intercom.com/installing-intercom/docs/ios-push-notifications) to enable push notification in iOS. +* Starting from Android 13 you may need to ask for notification permissions (as of version 13 `firebase_messaging` should support that) +* Ask FirebaseMessaging for the token that we need to send to Intercom, and give it to Intercom (so Intercom can send push messages to the correct device), please note that in order to receive push notifications in your iOS app, you have to send the APNS token to Intercom. The example below uses [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) to get either the FCM or APNS token based on the platform: ```dart final firebaseMessaging = FirebaseMessaging.instance; @@ -187,18 +175,15 @@ Intercom.instance.sendTokenToIntercom(intercomToken); Now, if either Firebase direct (e.g. by your own backend server) or Intercom sends you a message, it will be delivered to your app. ### Web - You don't need to do any extra steps for the web. Intercom script will be automatically injected. But you can pre-define some Intercom settings, if you want (optional). - ```html ``` - #### Following functions are not yet supported on Web: - [ ] unreadConversationCount @@ -212,18 +197,15 @@ But you can pre-define some Intercom settings, if you want (optional). ## Using Intercom keys with `--dart-define` -Use `--dart-define` variables to avoid hardcoding Intercom keys. +Use `--dart-define` variables to avoid hardcoding Intercom keys. ### Pass the Intercom keys with `flutter run` or `flutter build` command using `--dart-define`. - ```dart flutter run --dart-define="INTERCOM_APP_ID=appID" --dart-define="INTERCOM_ANDROID_KEY=androidKey" --dart-define="INTERCOM_IOS_KEY=iosKey" ``` - Note: You can also use `--dart-define-from-file` which is introduced in Flutter 3.7. ### Reading keys in Dart side and initialize the SDK. - ```dart String appId = String.fromEnvironment("INTERCOM_APP_ID", ""); String androidKey = String.fromEnvironment("INTERCOM_ANDROID_KEY", ""); @@ -234,8 +216,7 @@ Intercom.instance.initialize(appId, iosApiKey: iOSKey, androidApiKey: androidKey ### Reading keys in Android native side and initialize the SDK. -- Add the following code to `build.gradle`. - +* Add the following code to `build.gradle`. ``` def dartEnvironmentVariables = [] if (project.hasProperty('dart-defines')) { @@ -248,8 +229,7 @@ if (project.hasProperty('dart-defines')) { } ``` -- Place `dartEnvironmentVariables` inside the build config - +* Place `dartEnvironmentVariables` inside the build config ``` defaultConfig { ... @@ -258,8 +238,7 @@ defaultConfig { } ``` -- Read the build config fields - +* Read the build config fields ```kotlin import android.app.Application import android.os.Build @@ -268,10 +247,10 @@ import io.maido.intercom.IntercomFlutterPlugin class MyApp : Application() { override fun onCreate() { super.onCreate() - + // Add this line with your keys - IntercomFlutterPlugin.initSdk(this, - appId = BuildConfig.INTERCOM_APP_ID, + IntercomFlutterPlugin.initSdk(this, + appId = BuildConfig.INTERCOM_APP_ID, androidApiKey = BuildConfig.INTERCOM_ANDROID_KEY) } } From fd5d039d4664998c28dd01f94a6b6fa7bf70972e Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 6 Apr 2026 13:18:02 +0530 Subject: [PATCH 3/6] No needed as already available in example app --- intercom_flutter/README.md | 45 -------------------------------------- 1 file changed, 45 deletions(-) diff --git a/intercom_flutter/README.md b/intercom_flutter/README.md index c58d3559..28918c8b 100755 --- a/intercom_flutter/README.md +++ b/intercom_flutter/README.md @@ -50,51 +50,6 @@ class App extends StatelessWidget { See Intercom [Android](https://developers.intercom.com/installing-intercom/docs/intercom-for-android) and [iOS](https://developers.intercom.com/installing-intercom/docs/intercom-for-ios) package documentation for more information. -### Listening for Intercom Window Events - -You can listen for when the Intercom window is hidden (closed) using the `getWindowDidHideStream()` method. This is particularly useful for performing actions when users close the Intercom messenger, help center, or other Intercom windows. - -**Note:** This feature is only available on iOS. - -```dart -import 'package:flutter/material.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; -import 'dart:async'; - -class MyApp extends StatefulWidget { - @override - _MyAppState createState() => _MyAppState(); -} - -class _MyAppState extends State { - StreamSubscription? _windowDidHideSubscription; - - @override - void initState() { - super.initState(); - _windowDidHideSubscription = Intercom.instance.getWindowDidHideStream().listen((_) { - print('Intercom window was closed!'); - }); - } - - @override - void dispose() { - _windowDidHideSubscription?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return FlatButton( - child: Text('Open Intercom'), - onPressed: () async { - await Intercom.instance.displayMessenger(); - }, - ); - } -} -``` - ### Android Make sure that your app's `MainActivity` extends `FlutterFragmentActivity` (you can check the example). From e74a2178a5914da79eb320d5fe509cb5338e3e76 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 6 Apr 2026 13:24:05 +0530 Subject: [PATCH 4/6] Use dedicated WindowDidHideStreamHandler --- .../maido/intercom/IntercomFlutterPlugin.kt | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt b/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt index f83a895e..a233b993 100644 --- a/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt +++ b/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt @@ -14,6 +14,12 @@ import io.intercom.android.sdk.identity.Registration import io.intercom.android.sdk.push.IntercomPushClient import io.intercom.android.sdk.ui.theme.ThemeMode +// No-op stream handler for windowDidHide event since it's only supported on iOS. +class WindowDidHideStreamHandler : EventChannel.StreamHandler { + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {} + override fun onCancel(arguments: Any?) {} +} + class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ActivityAware { companion object { @JvmStatic @@ -34,7 +40,7 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str val unreadEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "maido.io/intercom/unread") unreadEventChannel.setStreamHandler(IntercomFlutterPlugin()) val windowDidHideEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "maido.io/intercom/windowDidHide") - windowDidHideEventChannel.setStreamHandler(IntercomFlutterPlugin()) + windowDidHideEventChannel.setStreamHandler(WindowDidHideStreamHandler()) application = flutterPluginBinding.applicationContext as Application } @@ -383,22 +389,14 @@ class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.Str } override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { - // Check if this is the unread stream or windowDidHide stream - // For unread stream, we set up the listener - // For windowDidHide stream, we don't set up anything since it's iOS-specific - if (arguments == null || arguments.toString().contains("unread") || arguments.toString().isEmpty()) { - // This is the unread stream - unreadConversationCountListener = UnreadConversationCountListener { count -> - val handler = android.os.Handler(android.os.Looper.getMainLooper()) - handler.post { - events?.success(count) - } - }.also { - Intercom.client().addUnreadConversationCountListener(it) + unreadConversationCountListener = UnreadConversationCountListener { count -> + val handler = android.os.Handler(android.os.Looper.getMainLooper()) + handler.post { + events?.success(count) } - } - // For windowDidHide stream, we don't need to do anything since it's iOS-specific - // The stream will not emit any events on Android + }.also { + Intercom.client().addUnreadConversationCountListener(it) + } } override fun onCancel(arguments: Any?) { From b6f6986809abf7bb297cdf9396ade64dafcb849d Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 6 Apr 2026 13:34:30 +0530 Subject: [PATCH 5/6] Bump versions --- intercom_flutter/CHANGELOG.md | 1 + intercom_flutter/pubspec.yaml | 4 ++-- intercom_flutter_platform_interface/CHANGELOG.md | 5 +++++ intercom_flutter_platform_interface/pubspec.yaml | 2 +- intercom_flutter_web/CHANGELOG.md | 5 +++++ intercom_flutter_web/pubspec.yaml | 4 ++-- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/intercom_flutter/CHANGELOG.md b/intercom_flutter/CHANGELOG.md index 310f3452..2df02c48 100755 --- a/intercom_flutter/CHANGELOG.md +++ b/intercom_flutter/CHANGELOG.md @@ -3,6 +3,7 @@ ## 9.6.2 * Removed deprecated `handlePushMessage` API (removed from native Intercom SDK) +* Added `getWindowDidHideStream` to listen for when the Intercom window is hidden (iOS only). ## 9.6.1 diff --git a/intercom_flutter/pubspec.yaml b/intercom_flutter/pubspec.yaml index cd0c870c..05fc6742 100644 --- a/intercom_flutter/pubspec.yaml +++ b/intercom_flutter/pubspec.yaml @@ -9,8 +9,8 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - intercom_flutter_platform_interface: ^2.0.6 - intercom_flutter_web: ^1.1.11 + intercom_flutter_platform_interface: ^2.0.7 + intercom_flutter_web: ^1.1.12 dev_dependencies: flutter_test: diff --git a/intercom_flutter_platform_interface/CHANGELOG.md b/intercom_flutter_platform_interface/CHANGELOG.md index a8c491d3..e4ee1eff 100755 --- a/intercom_flutter_platform_interface/CHANGELOG.md +++ b/intercom_flutter_platform_interface/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.0.7 + +* Added method `getWindowDidHideStream`. +* Removed deprecated `handlePushMessage`. + ## 2.0.6 * Added method `setThemeMode`. diff --git a/intercom_flutter_platform_interface/pubspec.yaml b/intercom_flutter_platform_interface/pubspec.yaml index 1d86bb16..ad4e8d51 100644 --- a/intercom_flutter_platform_interface/pubspec.yaml +++ b/intercom_flutter_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: intercom_flutter_platform_interface description: A common platform interface for the intercom_flutter plugin. -version: 2.0.6 +version: 2.0.7 homepage: https://github.com/v3rm0n/intercom_flutter dependencies: diff --git a/intercom_flutter_web/CHANGELOG.md b/intercom_flutter_web/CHANGELOG.md index 0581878e..2d5f8905 100755 --- a/intercom_flutter_web/CHANGELOG.md +++ b/intercom_flutter_web/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.1.12 + +* Updated `intercom_flutter_platform_interface` to `^2.0.7`. +* Removed deprecated `handlePushMessage`. + ## 1.1.11 * JSON-encode `intercomSettings` before injecting the Intercom script to avoid invalid JavaScript diff --git a/intercom_flutter_web/pubspec.yaml b/intercom_flutter_web/pubspec.yaml index 6fbea3e7..868d7ff5 100644 --- a/intercom_flutter_web/pubspec.yaml +++ b/intercom_flutter_web/pubspec.yaml @@ -1,6 +1,6 @@ name: intercom_flutter_web description: Web platform implementation of intercom_flutter -version: 1.1.11 +version: 1.1.12 homepage: https://github.com/v3rm0n/intercom_flutter flutter: @@ -15,7 +15,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - intercom_flutter_platform_interface: ^2.0.6 + intercom_flutter_platform_interface: ^2.0.7 uuid: ^4.2.1 # to get the random uuid for loginUnidentifiedUser in web web: ^1.0.0 From 99fe3ff3e44fed83a4b2272add1c0dc26e3507c4 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 6 Apr 2026 14:36:01 +0530 Subject: [PATCH 6/6] Add web check --- intercom_flutter/example/lib/main.dart | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/intercom_flutter/example/lib/main.dart b/intercom_flutter/example/lib/main.dart index e2a0e454..9487823f 100644 --- a/intercom_flutter/example/lib/main.dart +++ b/intercom_flutter/example/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; @@ -27,19 +28,21 @@ class _SampleAppState extends State { void initState() { super.initState(); // Listen for when the Intercom window is hidden - _windowDidHideSubscription = - Intercom.instance.getWindowDidHideStream().listen((_) { - // This will be called when the Intercom window is closed - // Only works on iOS - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Intercom window was closed!'), - duration: Duration(seconds: 2), - ), - ); - } - }); + if (!kIsWeb) { + _windowDidHideSubscription = + Intercom.instance.getWindowDidHideStream().listen((_) { + // This will be called when the Intercom window is closed + // Only works on iOS + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Intercom window was closed!'), + duration: Duration(seconds: 2), + ), + ); + } + }); + } } @override @@ -72,12 +75,14 @@ class _SampleAppState extends State { }, child: Text('Show messenger'), ), - SizedBox(height: 20), - Text( - 'Close the Intercom window to see the notification!', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), - ), + if (!kIsWeb) ...[ + SizedBox(height: 20), + Text( + 'Close the Intercom window to see the notification!', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ], ], ), ),