From ddfade47aa028ff691a3d9e5b3e3a751b3aa5548 Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Tue, 14 Nov 2023 22:58:54 +1030
Subject: [PATCH 01/12] Implement bubble support
---
.../FlutterLocalNotificationsPlugin.java | 26 +++++++++++++++++++
.../models/NotificationDetails.java | 3 +++
.../android/method_channel_mappers.dart | 1 +
.../android/notification_details.dart | 3 +++
4 files changed, 33 insertions(+)
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index 9ee5660cf..b50bc1c0f 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -415,6 +415,32 @@ protected static Notification createNotification(
setProgress(notificationDetails, builder);
setCategory(notificationDetails, builder);
setTimeoutAfter(notificationDetails, builder);
+
+
+ if(notificationDetails.bubbleActivity != null) {
+ try {
+ Class cls = Class.forName(notificationDetails.bubbleActivity);
+ Intent testIntent = new Intent(context, cls);
+ int actionFlags = PendingIntent.FLAG_MUTABLE;
+ PendingIntent bubbleIntent = PendingIntent.getActivity(context, notificationDetails.id, testIntent, actionFlags);
+
+ IconCompat icon = IconCompat.createWithResource(context, getDrawableResourceId(context, notificationDetails.icon));
+
+ if (!StringUtils.isNullOrEmpty(notificationDetails.shortcutId)) {
+
+ androidx.core.app.NotificationCompat.BubbleMetadata bubbleData =
+ new androidx.core.app.NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon)
+ .setDesiredHeight(600)
+ .build();
+
+ builder.setBubbleMetadata(bubbleData);
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+
Notification notification = builder.build();
if (notificationDetails.additionalFlags != null
&& notificationDetails.additionalFlags.length > 0) {
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
index 2f56f6ff5..217abca60 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
@@ -103,6 +103,7 @@ public class NotificationDetails implements Serializable {
private static final String VISIBILITY = "visibility";
private static final String TICKER = "ticker";
+ private static final String BUBBLE_ACTIVITY = "bubbleActivity";
private static final String SCHEDULE_MODE = "scheduleMode";
private static final String CATEGORY = "category";
private static final String TIMEOUT_AFTER = "timeoutAfter";
@@ -168,6 +169,7 @@ public class NotificationDetails implements Serializable {
public Integer ledOnMs;
public Integer ledOffMs;
public String ticker;
+ public String bubbleActivity;
public Integer visibility;
@SerializedName(value = "scheduleMode", alternate = "allowWhileIdle")
@@ -269,6 +271,7 @@ private static void readPlatformSpecifics(
readLedInformation(notificationDetails, platformChannelSpecifics);
readLargeIconInformation(notificationDetails, platformChannelSpecifics);
notificationDetails.ticker = (String) platformChannelSpecifics.get(TICKER);
+ notificationDetails.bubbleActivity = (String) platformChannelSpecifics.get(BUBBLE_ACTIVITY);
notificationDetails.visibility = (Integer) platformChannelSpecifics.get(VISIBILITY);
if (platformChannelSpecifics.containsKey(SCHEDULE_MODE)) {
notificationDetails.scheduleMode =
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
index f808d77b0..9c43e5105 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
@@ -209,6 +209,7 @@ extension AndroidNotificationDetailsMapper on AndroidNotificationDetails {
'ledOnMs': ledOnMs,
'ledOffMs': ledOffMs,
'ticker': ticker,
+ 'bubbleActivity': bubbleActivity,
'visibility': visibility?.index,
'timeoutAfter': timeoutAfter,
'category': category?.name,
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
index bf7ffff8f..50ed21f4a 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
@@ -133,6 +133,7 @@ class AndroidNotificationDetails {
this.ledOnMs,
this.ledOffMs,
this.ticker,
+ this.bubbleActivity,
this.visibility,
this.timeoutAfter,
this.category,
@@ -310,6 +311,8 @@ class AndroidNotificationDetails {
/// Specifies the "ticker" text which is sent to accessibility services.
final String? ticker;
+ final String? bubbleActivity;
+
/// The action to take for managing notification channels.
///
/// Defaults to creating the notification channel using the provided details
From 7e3ae5a5bff96ed74cbf4c5453618845b2c624c7 Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Wed, 15 Nov 2023 12:32:48 +1030
Subject: [PATCH 02/12] Add way to pass extra data to bubble intent
---
.../FlutterLocalNotificationsPlugin.java | 16 ++++++++++++++--
.../models/NotificationDetails.java | 3 +++
.../android/method_channel_mappers.dart | 1 +
.../android/notification_details.dart | 3 +++
4 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index b50bc1c0f..97dccd0a4 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -421,11 +421,22 @@ protected static Notification createNotification(
try {
Class cls = Class.forName(notificationDetails.bubbleActivity);
Intent testIntent = new Intent(context, cls);
- int actionFlags = PendingIntent.FLAG_MUTABLE;
- PendingIntent bubbleIntent = PendingIntent.getActivity(context, notificationDetails.id, testIntent, actionFlags);
+ testIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if(notificationDetails.bubbleExtra != null) {
+ final Bundle extra = new Bundle();
+ extra.putString("bubbleExtra", notificationDetails.bubbleExtra);
+ testIntent.putExtras(extra);
+ }
+
+ int actionFlags = PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
+ PendingIntent bubbleIntent = PendingIntent.getActivity(context, notificationDetails.id, testIntent, actionFlags);
IconCompat icon = IconCompat.createWithResource(context, getDrawableResourceId(context, notificationDetails.icon));
+ Log.e(TAG, "Created pending intent: $bubbleIntent");
+ Log.e(TAG, bubbleIntent.toString());
+
if (!StringUtils.isNullOrEmpty(notificationDetails.shortcutId)) {
androidx.core.app.NotificationCompat.BubbleMetadata bubbleData =
@@ -433,6 +444,7 @@ protected static Notification createNotification(
.setDesiredHeight(600)
.build();
+ builder.setContentIntent(bubbleIntent);
builder.setBubbleMetadata(bubbleData);
}
} catch (ClassNotFoundException e) {
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
index 217abca60..9928b8b71 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
@@ -104,6 +104,7 @@ public class NotificationDetails implements Serializable {
private static final String TICKER = "ticker";
private static final String BUBBLE_ACTIVITY = "bubbleActivity";
+ private static final String BUBBLE_EXTRA = "bubbleExtra";
private static final String SCHEDULE_MODE = "scheduleMode";
private static final String CATEGORY = "category";
private static final String TIMEOUT_AFTER = "timeoutAfter";
@@ -170,6 +171,7 @@ public class NotificationDetails implements Serializable {
public Integer ledOffMs;
public String ticker;
public String bubbleActivity;
+ public String bubbleExtra;
public Integer visibility;
@SerializedName(value = "scheduleMode", alternate = "allowWhileIdle")
@@ -272,6 +274,7 @@ private static void readPlatformSpecifics(
readLargeIconInformation(notificationDetails, platformChannelSpecifics);
notificationDetails.ticker = (String) platformChannelSpecifics.get(TICKER);
notificationDetails.bubbleActivity = (String) platformChannelSpecifics.get(BUBBLE_ACTIVITY);
+ notificationDetails.bubbleExtra = (String) platformChannelSpecifics.get(BUBBLE_EXTRA);
notificationDetails.visibility = (Integer) platformChannelSpecifics.get(VISIBILITY);
if (platformChannelSpecifics.containsKey(SCHEDULE_MODE)) {
notificationDetails.scheduleMode =
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
index 9c43e5105..67facb4a0 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
@@ -210,6 +210,7 @@ extension AndroidNotificationDetailsMapper on AndroidNotificationDetails {
'ledOffMs': ledOffMs,
'ticker': ticker,
'bubbleActivity': bubbleActivity,
+ 'bubbleExtra': bubbleExtra,
'visibility': visibility?.index,
'timeoutAfter': timeoutAfter,
'category': category?.name,
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
index 50ed21f4a..69d80034b 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
@@ -134,6 +134,7 @@ class AndroidNotificationDetails {
this.ledOffMs,
this.ticker,
this.bubbleActivity,
+ this.bubbleExtra,
this.visibility,
this.timeoutAfter,
this.category,
@@ -313,6 +314,8 @@ class AndroidNotificationDetails {
final String? bubbleActivity;
+ final String? bubbleExtra;
+
/// The action to take for managing notification channels.
///
/// Defaults to creating the notification channel using the provided details
From a502fb4559c76fd70e23ba606c9b3452b0360daf Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Wed, 15 Nov 2023 18:20:48 +1030
Subject: [PATCH 03/12] Update FlutterLocalNotificationsPlugin.java
---
.../FlutterLocalNotificationsPlugin.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index 97dccd0a4..ff40f14d5 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -444,7 +444,6 @@ protected static Notification createNotification(
.setDesiredHeight(600)
.build();
- builder.setContentIntent(bubbleIntent);
builder.setBubbleMetadata(bubbleData);
}
} catch (ClassNotFoundException e) {
From 2ed87e412371cb5aeaceaced0dd49bd812ea85be Mon Sep 17 00:00:00 2001
From: github-actions <>
Date: Mon, 20 Nov 2023 01:47:04 +0000
Subject: [PATCH 04/12] Google Java Format
---
.../FlutterLocalNotificationsPlugin.java | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index e2f901df0..395f5c755 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -417,23 +417,25 @@ protected static Notification createNotification(
setCategory(notificationDetails, builder);
setTimeoutAfter(notificationDetails, builder);
-
- if(notificationDetails.bubbleActivity != null) {
+ if (notificationDetails.bubbleActivity != null) {
try {
Class cls = Class.forName(notificationDetails.bubbleActivity);
Intent testIntent = new Intent(context, cls);
testIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if(notificationDetails.bubbleExtra != null) {
+ if (notificationDetails.bubbleExtra != null) {
final Bundle extra = new Bundle();
extra.putString("bubbleExtra", notificationDetails.bubbleExtra);
testIntent.putExtras(extra);
}
int actionFlags = PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
- PendingIntent bubbleIntent = PendingIntent.getActivity(context, notificationDetails.id, testIntent, actionFlags);
+ PendingIntent bubbleIntent =
+ PendingIntent.getActivity(context, notificationDetails.id, testIntent, actionFlags);
- IconCompat icon = IconCompat.createWithResource(context, getDrawableResourceId(context, notificationDetails.icon));
+ IconCompat icon =
+ IconCompat.createWithResource(
+ context, getDrawableResourceId(context, notificationDetails.icon));
Log.e(TAG, "Created pending intent: $bubbleIntent");
Log.e(TAG, bubbleIntent.toString());
@@ -441,9 +443,9 @@ protected static Notification createNotification(
if (!StringUtils.isNullOrEmpty(notificationDetails.shortcutId)) {
androidx.core.app.NotificationCompat.BubbleMetadata bubbleData =
- new androidx.core.app.NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon)
- .setDesiredHeight(600)
- .build();
+ new androidx.core.app.NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon)
+ .setDesiredHeight(600)
+ .build();
builder.setBubbleMetadata(bubbleData);
}
@@ -451,7 +453,6 @@ protected static Notification createNotification(
e.printStackTrace();
}
}
-
Notification notification = builder.build();
if (notificationDetails.additionalFlags != null
From 0cdda35be48014df8c56ecb5294a5acc68c42a00 Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Sun, 22 Jun 2025 14:10:36 +0930
Subject: [PATCH 05/12] reworking bubble implementation
---
.../FlutterLocalNotificationsPlugin.java | 17 +++++++++++++----
.../models/NotificationDetails.java | 6 ++++++
.../src/platform_specifics/android/bubble.dart | 15 +++++++++++++++
.../android/method_channel_mappers.dart | 5 +++--
.../android/notification_details.dart | 10 +++++-----
5 files changed, 42 insertions(+), 11 deletions(-)
create mode 100644 flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index 7f8042614..d2e982d2a 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -458,11 +458,20 @@ protected static Notification createNotification(
if (!StringUtils.isNullOrEmpty(notificationDetails.shortcutId)) {
- androidx.core.app.NotificationCompat.BubbleMetadata bubbleData =
- new androidx.core.app.NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon)
- .setDesiredHeight(600)
- .build();
+ var bubbleBuilder =
+ new androidx.core.app.NotificationCompat.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(icon);
+ if(notificationDetails.bubbleDesiredHeight != null) {
+ bubbleBuilder.setDesiredHeight(notificationDetails.bubbleDesiredHeight);
+ }
+
+ if(notificationDetails.bubbleAutoExpand != null) {
+ bubbleBuilder.setAutoExpandBubble(notificationDetails.bubbleAutoExpand);
+ }
+
+ var bubbleData = bubbleBuilder.build();
builder.setBubbleMetadata(bubbleData);
}
} catch (ClassNotFoundException e) {
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
index 764a4e85b..d40127250 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
@@ -108,6 +108,8 @@ public class NotificationDetails implements Serializable {
private static final String TICKER = "ticker";
private static final String BUBBLE_ACTIVITY = "bubbleActivity";
private static final String BUBBLE_EXTRA = "bubbleExtra";
+ private static final String BUBBLE_AUTO_EXPAND = "bubbleAutoExpand";
+ private static final String BUBBLE_DESIRED_HEIGHT = "bubbleDesiredHeight";
private static final String SCHEDULE_MODE = "scheduleMode";
private static final String CATEGORY = "category";
private static final String TIMEOUT_AFTER = "timeoutAfter";
@@ -178,6 +180,8 @@ public class NotificationDetails implements Serializable {
public String ticker;
public String bubbleActivity;
public String bubbleExtra;
+ public Integer bubbleDesiredHeight;
+ public Boolean bubbleAutoExpand;
public Integer visibility;
@SerializedName(value = "scheduleMode", alternate = "allowWhileIdle")
@@ -286,6 +290,8 @@ private static void readPlatformSpecifics(
notificationDetails.ticker = (String) platformChannelSpecifics.get(TICKER);
notificationDetails.bubbleActivity = (String) platformChannelSpecifics.get(BUBBLE_ACTIVITY);
notificationDetails.bubbleExtra = (String) platformChannelSpecifics.get(BUBBLE_EXTRA);
+ notificationDetails.bubbleAutoExpand = (Boolean) platformChannelSpecifics.get(BUBBLE_AUTO_EXPAND);
+ notificationDetails.bubbleDesiredHeight = (Integer) platformChannelSpecifics.get(BUBBLE_DESIRED_HEIGHT);
notificationDetails.visibility = (Integer) platformChannelSpecifics.get(VISIBILITY);
if (platformChannelSpecifics.containsKey(SCHEDULE_MODE)) {
notificationDetails.scheduleMode =
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart b/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart
new file mode 100644
index 000000000..cbbff7143
--- /dev/null
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart
@@ -0,0 +1,15 @@
+/// Defines metadata to be set for the notification's chat bubble
+/// See: https://developer.android.com/develop/ui/views/notifications/bubbles
+class BubbleMetadata {
+ final String activity;
+ final String? extra;
+ final int? desiredHeight;
+ final bool? autoExpand;
+
+ BubbleMetadata(
+ this.activity, {
+ this.extra,
+ this.autoExpand,
+ this.desiredHeight,
+ });
+}
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
index 89e4cb5d8..0b8398d4e 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
@@ -213,8 +213,9 @@ extension AndroidNotificationDetailsMapper on AndroidNotificationDetails {
'ledOnMs': ledOnMs,
'ledOffMs': ledOffMs,
'ticker': ticker,
- 'bubbleActivity': bubbleActivity,
- 'bubbleExtra': bubbleExtra,
+ 'bubbleActivity': bubble?.activity,
+ 'bubbleExtra': bubble?.extra,
+ 'bubbleDesiredHeight': bubble?.desiredHeight,
'visibility': visibility?.index,
'timeoutAfter': timeoutAfter,
'category': category?.name,
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
index 9745442ba..01b5570e7 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart
@@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'dart:ui';
import 'bitmap.dart';
+import 'bubble.dart';
import 'enums.dart';
import 'notification_sound.dart';
import 'styles/style_information.dart';
@@ -135,8 +136,7 @@ class AndroidNotificationDetails {
this.ledOnMs,
this.ledOffMs,
this.ticker,
- this.bubbleActivity,
- this.bubbleExtra,
+ this.bubble,
this.visibility,
this.timeoutAfter,
this.category,
@@ -325,9 +325,9 @@ class AndroidNotificationDetails {
/// Specifies the "ticker" text which is sent to accessibility services.
final String? ticker;
- final String? bubbleActivity;
-
- final String? bubbleExtra;
+ /// Specifies BubbleMetadata to be passed to the notification, allowing use of
+ /// Android conversation bubbles api.
+ final BubbleMetadata? bubble;
/// The action to take for managing notification channels.
///
From 866c9b742ba9d85471ad6700eac25a97827a85c6 Mon Sep 17 00:00:00 2001
From: github-actions <>
Date: Sun, 22 Jun 2025 04:40:56 +0000
Subject: [PATCH 06/12] Google Java Format
---
.../FlutterLocalNotificationsPlugin.java | 4 ++--
.../models/NotificationDetails.java | 6 ++++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index d2e982d2a..c6746a1e5 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -463,11 +463,11 @@ protected static Notification createNotification(
.setIntent(bubbleIntent)
.setIcon(icon);
- if(notificationDetails.bubbleDesiredHeight != null) {
+ if (notificationDetails.bubbleDesiredHeight != null) {
bubbleBuilder.setDesiredHeight(notificationDetails.bubbleDesiredHeight);
}
- if(notificationDetails.bubbleAutoExpand != null) {
+ if (notificationDetails.bubbleAutoExpand != null) {
bubbleBuilder.setAutoExpandBubble(notificationDetails.bubbleAutoExpand);
}
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
index d40127250..40bc4a534 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java
@@ -290,8 +290,10 @@ private static void readPlatformSpecifics(
notificationDetails.ticker = (String) platformChannelSpecifics.get(TICKER);
notificationDetails.bubbleActivity = (String) platformChannelSpecifics.get(BUBBLE_ACTIVITY);
notificationDetails.bubbleExtra = (String) platformChannelSpecifics.get(BUBBLE_EXTRA);
- notificationDetails.bubbleAutoExpand = (Boolean) platformChannelSpecifics.get(BUBBLE_AUTO_EXPAND);
- notificationDetails.bubbleDesiredHeight = (Integer) platformChannelSpecifics.get(BUBBLE_DESIRED_HEIGHT);
+ notificationDetails.bubbleAutoExpand =
+ (Boolean) platformChannelSpecifics.get(BUBBLE_AUTO_EXPAND);
+ notificationDetails.bubbleDesiredHeight =
+ (Integer) platformChannelSpecifics.get(BUBBLE_DESIRED_HEIGHT);
notificationDetails.visibility = (Integer) platformChannelSpecifics.get(VISIBILITY);
if (platformChannelSpecifics.containsKey(SCHEDULE_MODE)) {
notificationDetails.scheduleMode =
From fe814cc30eac34ac024f9b8f6ff827b53878ed34 Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Sun, 22 Jun 2025 15:05:38 +0930
Subject: [PATCH 07/12] Add bubble notification to example
---
.../android/app/src/main/AndroidManifest.xml | 20 +++++
.../MainActivity.kt | 9 +++
.../example/lib/main.dart | 77 +++++++++++++++++++
.../example/pubspec.yaml | 1 +
.../lib/flutter_local_notifications.dart | 2 +-
5 files changed, 108 insertions(+), 1 deletion(-)
diff --git a/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml b/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml
index 266a44217..6ce9d2f81 100644
--- a/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml
+++ b/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml
@@ -40,6 +40,26 @@
+
+
+
+
+
{
await _showNotificationCustomSound();
},
),
+ if (Platform.isAndroid)
+ PaddedElevatedButton(
+ buttonText:
+ 'Show notification with conversation bubble',
+ onPressed: _showNotificationWithBubble),
if (kIsWeb || !Platform.isLinux) ...[
PaddedElevatedButton(
buttonText:
@@ -1402,6 +1419,66 @@ class _HomePageState extends State {
);
}
+ Future _showNotificationWithBubble() async {
+ final FlutterShortcuts shortcuts = FlutterShortcuts();
+ const String shortcutId = 'my_conversation_shortcut';
+ const ShortcutItem item = ShortcutItem(
+ id: shortcutId,
+ action: 'some_action',
+ shortLabel: 'Conversation Shortcut',
+ icon: 'icons/coworker.png',
+ conversationShortcut: true);
+
+ await shortcuts.pushShortcutItem(shortcut: item);
+
+ final MessagingStyleInformation? activeStyleInfo =
+ await AndroidFlutterLocalNotificationsPlugin()
+ .getActiveNotificationMessagingStyle(0);
+
+ const Person person = Person(
+ name: 'Coworker',
+ key: '2',
+ uri: 'tel:9876543210',
+ icon: FlutterBitmapAssetAndroidIcon('icons/coworker.png'),
+ );
+
+ final Message message = Message(
+ 'This is a test message',
+ DateTime.now(),
+ person,
+ );
+
+ activeStyleInfo?.messages?.add(message);
+
+ final MessagingStyleInformation style = activeStyleInfo ??
+ MessagingStyleInformation(person,
+ conversationTitle: 'Conversation',
+ messages: [message],
+ groupConversation: false);
+
+ final AndroidNotificationDetails details = AndroidNotificationDetails(
+ 'messages', 'Message Received',
+ importance: Importance.high,
+ priority: Priority.high,
+ icon: 'secondary_icon',
+ number: style.messages!.length,
+ subText: 'Test Message',
+ styleInformation: style,
+ shortcutId:
+ shortcutId, // Has to be same as the shortcut created earlier, in a real use case probably would be the ID of a chat room / user
+ ticker: 'Test Message',
+ bubble: BubbleMetadata(
+ 'com.dexterous.flutter_local_notifications_example.BubbleActivity',
+ desiredHeight: 600,
+ autoExpand: false,
+ extra: 'bubble_extra_data'),
+ color: const Color.fromARGB(0xff, 0x53, 0x4c, 0xdd));
+
+ await flutterLocalNotificationsPlugin.show(
+ id, null, 'Test Message', NotificationDetails(android: details),
+ payload: 'some_message_payload');
+ }
+
Future _showNotificationCustomVibrationIconLed() async {
final Int64List vibrationPattern = Int64List(4);
vibrationPattern[0] = 0;
diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml
index 21edacde6..2f33e65b2 100644
--- a/flutter_local_notifications/example/pubspec.yaml
+++ b/flutter_local_notifications/example/pubspec.yaml
@@ -9,6 +9,7 @@ dependencies:
sdk: flutter
flutter_local_notifications:
path: ../
+ flutter_shortcuts_new: ^2.0.0
flutter_timezone: ^4.0.1
http: ^1.3.0
image: ^4.5.2
diff --git a/flutter_local_notifications/lib/flutter_local_notifications.dart b/flutter_local_notifications/lib/flutter_local_notifications.dart
index c42e2ff4c..08c93343d 100644
--- a/flutter_local_notifications/lib/flutter_local_notifications.dart
+++ b/flutter_local_notifications/lib/flutter_local_notifications.dart
@@ -10,6 +10,7 @@ export 'src/notification_details.dart';
export 'src/platform_flutter_local_notifications.dart'
hide MethodChannelFlutterLocalNotificationsPlugin;
export 'src/platform_specifics/android/bitmap.dart';
+export 'src/platform_specifics/android/bubble.dart';
export 'src/platform_specifics/android/enums.dart'
hide AndroidBitmapSource, AndroidIconSource, AndroidNotificationSoundSource;
export 'src/platform_specifics/android/icon.dart' hide AndroidIcon;
@@ -37,6 +38,5 @@ export 'src/platform_specifics/darwin/notification_category.dart';
export 'src/platform_specifics/darwin/notification_category_option.dart';
export 'src/platform_specifics/darwin/notification_details.dart';
export 'src/platform_specifics/darwin/notification_enabled_options.dart';
-
export 'src/typedefs.dart';
export 'src/types.dart';
From f5e89113db580bdd46e7821ef7f8c759238b99bf Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Sun, 22 Jun 2025 15:16:45 +0930
Subject: [PATCH 08/12] add reading bubble intent to example
---
.../example/lib/main.dart | 27 +++++++++++++++----
.../example/pubspec.yaml | 1 +
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/flutter_local_notifications/example/lib/main.dart b/flutter_local_notifications/example/lib/main.dart
index 54ee12d99..6156c6db9 100644
--- a/flutter_local_notifications/example/lib/main.dart
+++ b/flutter_local_notifications/example/lib/main.dart
@@ -14,6 +14,7 @@ import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:http/http.dart' as http;
import 'package:image/image.dart' as image;
import 'package:path_provider/path_provider.dart';
+import 'package:receive_intent/receive_intent.dart' as receive_intent;
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
@@ -76,11 +77,27 @@ void notificationTapBackground(NotificationResponse notificationResponse) {
}
@pragma('vm:entry-point')
-void bubbleEntry() {
+Future bubbleEntry() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ final receive_intent.Intent? intent =
+ await receive_intent.ReceiveIntent.getInitialIntent();
+ String? extraData;
+ if (intent?.extra?.containsKey('bubbleExtra') == true) {
+ extraData = intent!.extra!['bubbleExtra'] as String;
+ }
+
runApp(
- const MaterialApp(
+ MaterialApp(
home: Scaffold(
- body: Center(child: Text('This is your bubble UI')),
+ body: Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Text('This is your bubble UI'),
+ Text('created with extra data: $extraData'),
+ ],
+ )),
),
),
);
@@ -1469,9 +1486,9 @@ class _HomePageState extends State {
ticker: 'Test Message',
bubble: BubbleMetadata(
'com.dexterous.flutter_local_notifications_example.BubbleActivity',
- desiredHeight: 600,
+ desiredHeight: 1000,
autoExpand: false,
- extra: 'bubble_extra_data'),
+ extra: 'my_bubble_extra_data'),
color: const Color.fromARGB(0xff, 0x53, 0x4c, 0xdd));
await flutterLocalNotificationsPlugin.show(
diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml
index 2f33e65b2..51ea85f38 100644
--- a/flutter_local_notifications/example/pubspec.yaml
+++ b/flutter_local_notifications/example/pubspec.yaml
@@ -14,6 +14,7 @@ dependencies:
http: ^1.3.0
image: ^4.5.2
path_provider: ^2.1.5
+ receive_intent: ^0.2.7
timezone: ^0.10.0
dev_dependencies:
From 0891e4eef6f8eb2109f82feac38db6e51a23e554 Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Sun, 22 Jun 2025 15:25:41 +0930
Subject: [PATCH 09/12] document BubbleMetadata
---
.../platform_specifics/android/bubble.dart | 24 +++++++++++++++----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart b/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart
index cbbff7143..89bd62793 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/bubble.dart
@@ -1,15 +1,29 @@
/// Defines metadata to be set for the notification's chat bubble
/// See: https://developer.android.com/develop/ui/views/notifications/bubbles
class BubbleMetadata {
- final String activity;
- final String? extra;
- final int? desiredHeight;
- final bool? autoExpand;
-
+ /// Encapsulates the information needed to display a notification as a bubble.
+ /// A bubble is used to display app content in a floating window over the
+ /// existing foreground activity. A bubble has a collapsed state represented
+ /// by an icon and an expanded state that displays an activity.
+ /// - https://developer.android.com/reference/android/app/Notification.BubbleMetadata
BubbleMetadata(
this.activity, {
this.extra,
this.autoExpand,
this.desiredHeight,
});
+
+ /// Define which activity should be started by the bubble
+ /// This activity needs to be declared in your android Manfiest.xml
+ /// As well as in the native code
+ final String activity;
+
+ /// Pass additional data to the bubble activities intent
+ final String? extra;
+
+ /// the ideal height, in DPs, for the floating window created by this activity
+ final int? desiredHeight;
+
+ /// whether this bubble should auto expand when it is posted.
+ final bool? autoExpand;
}
From 5badecb74e5ed1cfbf8976428a20254be6c1dfca Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Sun, 22 Jun 2025 15:33:29 +0930
Subject: [PATCH 10/12] Update FlutterLocalNotificationsPlugin.java
---
.../FlutterLocalNotificationsPlugin.java | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
index c6746a1e5..7de668f1e 100644
--- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
+++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java
@@ -436,31 +436,28 @@ protected static Notification createNotification(
if (notificationDetails.bubbleActivity != null) {
try {
Class cls = Class.forName(notificationDetails.bubbleActivity);
- Intent testIntent = new Intent(context, cls);
- testIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Intent bubbleIntent = new Intent(context, cls);
+ bubbleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (notificationDetails.bubbleExtra != null) {
final Bundle extra = new Bundle();
extra.putString("bubbleExtra", notificationDetails.bubbleExtra);
- testIntent.putExtras(extra);
+ bubbleIntent.putExtras(extra);
}
int actionFlags = PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
- PendingIntent bubbleIntent =
- PendingIntent.getActivity(context, notificationDetails.id, testIntent, actionFlags);
+ PendingIntent pendingBubbleIntent =
+ PendingIntent.getActivity(context, notificationDetails.id, bubbleIntent, actionFlags);
IconCompat icon =
IconCompat.createWithResource(
context, getDrawableResourceId(context, notificationDetails.icon));
- Log.e(TAG, "Created pending intent: $bubbleIntent");
- Log.e(TAG, bubbleIntent.toString());
-
if (!StringUtils.isNullOrEmpty(notificationDetails.shortcutId)) {
var bubbleBuilder =
new androidx.core.app.NotificationCompat.BubbleMetadata.Builder()
- .setIntent(bubbleIntent)
+ .setIntent(pendingBubbleIntent)
.setIcon(icon);
if (notificationDetails.bubbleDesiredHeight != null) {
From 5668a22a0842bb3894d51ebdd6df5904cbd25c4c Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Sun, 22 Jun 2025 15:36:33 +0930
Subject: [PATCH 11/12] Update method_channel_mappers.dart
---
.../src/platform_specifics/android/method_channel_mappers.dart | 1 +
1 file changed, 1 insertion(+)
diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
index 0b8398d4e..07989bc18 100644
--- a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
+++ b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart
@@ -216,6 +216,7 @@ extension AndroidNotificationDetailsMapper on AndroidNotificationDetails {
'bubbleActivity': bubble?.activity,
'bubbleExtra': bubble?.extra,
'bubbleDesiredHeight': bubble?.desiredHeight,
+ 'bubbleAutoExpand': bubble?.autoExpand,
'visibility': visibility?.index,
'timeoutAfter': timeoutAfter,
'category': category?.name,
From 93f906985388835d24eea13ae6ccd8ee656f2cf1 Mon Sep 17 00:00:00 2001
From: Airyzz <36567925+Airyzz@users.noreply.github.com>
Date: Wed, 5 Nov 2025 01:53:50 +1030
Subject: [PATCH 12/12] Add support for linux inline reply (#1)
---
.../lib/src/notifications_manager.dart | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/flutter_local_notifications_linux/lib/src/notifications_manager.dart b/flutter_local_notifications_linux/lib/src/notifications_manager.dart
index 0fe413ced..3e9c076cf 100644
--- a/flutter_local_notifications_linux/lib/src/notifications_manager.dart
+++ b/flutter_local_notifications_linux/lib/src/notifications_manager.dart
@@ -369,6 +369,34 @@ class LinuxNotificationManager {
},
);
+ _dbus.subscribeSignal(_DBusMethodsSpec.notificationReplied).listen(
+ (DBusSignal s) async {
+ if (s.signature != DBusSignature('us')) {
+ return;
+ }
+
+ final int systemId = (s.values[0] as DBusUint32).value;
+ final String text = (s.values[1] as DBusString).value;
+
+ final LinuxNotificationInfo? notify =
+ await _storage.getBySystemId(systemId);
+ if (notify == null) {
+ return;
+ }
+
+ _onDidReceiveNotificationResponse?.call(
+ NotificationResponse(
+ id: notify.id,
+ actionId: 'inline-reply',
+ payload: notify.payload,
+ input: text,
+ notificationResponseType:
+ NotificationResponseType.selectedNotificationAction,
+ ),
+ );
+ },
+ );
+
_dbus.subscribeSignal(_DBusMethodsSpec.notificationClosed).listen(
(DBusSignal s) async {
if (s.signature != DBusSignature('uu')) {
@@ -393,6 +421,7 @@ class _DBusMethodsSpec {
static const String notify = 'Notify';
static const String closeNotification = 'CloseNotification';
static const String actionInvoked = 'ActionInvoked';
+ static const String notificationReplied = 'NotificationReplied';
static const String notificationClosed = 'NotificationClosed';
static const String getCapabilities = 'GetCapabilities';
}