Skip to content

Commit

Permalink
Added support for active notifications on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
morvagergely committed Nov 24, 2021
1 parent a5a84f0 commit a7c8348
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ public class FlutterLocalNotificationsPlugin
"deleteNotificationChannelGroup";
private static final String CREATE_NOTIFICATION_CHANNEL_METHOD = "createNotificationChannel";
private static final String DELETE_NOTIFICATION_CHANNEL_METHOD = "deleteNotificationChannel";
private static final String GET_ACTIVE_NOTIFICATIONS_METHOD = "getActiveNotifications";
private static final String GET_NOTIFICATION_CHANNELS_METHOD = "getNotificationChannels";
private static final String START_FOREGROUND_SERVICE = "startForegroundService";
private static final String STOP_FOREGROUND_SERVICE = "stopForegroundService";
private static final String PENDING_NOTIFICATION_REQUESTS_METHOD = "pendingNotificationRequests";
private static final String ACTIVE_NOTIFICATIONS_METHOD = "getActiveNotifications";
private static final String SHOW_METHOD = "show";
private static final String CANCEL_METHOD = "cancel";
private static final String CANCEL_ALL_METHOD = "cancelAll";
Expand Down Expand Up @@ -1266,6 +1266,9 @@ public void onMethodCall(MethodCall call, Result result) {
case PENDING_NOTIFICATION_REQUESTS_METHOD:
pendingNotificationRequests(result);
break;
case ACTIVE_NOTIFICATIONS_METHOD:
getActiveNotifications(result);
break;
case CREATE_NOTIFICATION_CHANNEL_GROUP_METHOD:
createNotificationChannelGroup(call, result);
break;
Expand All @@ -1278,9 +1281,6 @@ public void onMethodCall(MethodCall call, Result result) {
case DELETE_NOTIFICATION_CHANNEL_METHOD:
deleteNotificationChannel(call, result);
break;
case GET_ACTIVE_NOTIFICATIONS_METHOD:
getActiveNotifications(result);
break;
case GET_NOTIFICATION_CHANNELS_METHOD:
getNotificationChannels(result);
break;
Expand Down Expand Up @@ -1311,6 +1311,38 @@ private void pendingNotificationRequests(Result result) {
}
result.success(pendingNotifications);
}

private void getActiveNotifications(Result result) {
if (VERSION.SDK_INT < VERSION_CODES.M) {
result.error(
GET_ACTIVE_NOTIFICATIONS_ERROR_CODE, GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE, null);
return;
}
NotificationManager notificationManager =
(NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
try {
StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();
List<Map<String, Object>> activeNotificationsPayload = new ArrayList<>();

for (StatusBarNotification activeNotification : activeNotifications) {
HashMap<String, Object> activeNotificationPayload = new HashMap<>();
activeNotificationPayload.put("id", activeNotification.getId());
Notification notification = activeNotification.getNotification();
if (VERSION.SDK_INT >= VERSION_CODES.O) {
activeNotificationPayload.put("channelId", notification.getChannelId());
}

activeNotificationPayload.put("groupKey", notification.getGroup());
activeNotificationPayload.put(
"title", notification.extras.getCharSequence("android.title"));
activeNotificationPayload.put("body", notification.extras.getCharSequence("android.text"));
activeNotificationsPayload.add(activeNotificationPayload);
}
result.success(activeNotificationsPayload);
} catch (Throwable e) {
result.error(GET_ACTIVE_NOTIFICATIONS_ERROR_CODE, e.getMessage(), e.getStackTrace());
}
}

private void cancel(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
Expand Down Expand Up @@ -1586,38 +1618,6 @@ private void deleteNotificationChannel(MethodCall call, Result result) {
result.success(null);
}

private void getActiveNotifications(Result result) {
if (VERSION.SDK_INT < VERSION_CODES.M) {
result.error(
GET_ACTIVE_NOTIFICATIONS_ERROR_CODE, GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE, null);
return;
}
NotificationManager notificationManager =
(NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
try {
StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();
List<Map<String, Object>> activeNotificationsPayload = new ArrayList<>();

for (StatusBarNotification activeNotification : activeNotifications) {
HashMap<String, Object> activeNotificationPayload = new HashMap<>();
activeNotificationPayload.put("id", activeNotification.getId());
Notification notification = activeNotification.getNotification();
if (VERSION.SDK_INT >= VERSION_CODES.O) {
activeNotificationPayload.put("channelId", notification.getChannelId());
}

activeNotificationPayload.put("groupKey", notification.getGroup());
activeNotificationPayload.put(
"title", notification.extras.getCharSequence("android.title"));
activeNotificationPayload.put("body", notification.extras.getCharSequence("android.text"));
activeNotificationsPayload.add(activeNotificationPayload);
}
result.success(activeNotificationsPayload);
} catch (Throwable e) {
result.error(GET_ACTIVE_NOTIFICATIONS_ERROR_CODE, e.getMessage(), e.getStackTrace());
}
}

private void getNotificationChannels(Result result) {
try {
NotificationManagerCompat notificationManagerCompat =
Expand Down
43 changes: 27 additions & 16 deletions flutter_local_notifications/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,12 @@ class _HomePageState extends State<HomePage> {
await _checkPendingNotificationRequests();
},
),
PaddedElevatedButton(
buttonText: 'Get active notifications',
onPressed: () async {
await _getActiveNotifications();
},
),
],
PaddedElevatedButton(
buttonText:
Expand Down Expand Up @@ -600,12 +606,6 @@ class _HomePageState extends State<HomePage> {
await _getNotificationChannels();
},
),
PaddedElevatedButton(
buttonText: 'Get active notifications',
onPressed: () async {
await _getActiveNotifications();
},
),
PaddedElevatedButton(
buttonText: 'Start foreground service',
onPressed: () async {
Expand Down Expand Up @@ -1927,20 +1927,31 @@ class _HomePageState extends State<HomePage> {
}

Future<Widget> _getActiveNotificationsDialogContent() async {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if (!(androidInfo.version.sdkInt >= 23)) {
return const Text(
'"getActiveNotifications" is available only for Android 6.0 or newer',
);
if (Platform.isAndroid) {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
if (androidInfo.version.sdkInt < 23) {
return const Text(
'"getActiveNotifications" is available only for Android 6.0 or newer',
);
}
} else if (Platform.isIOS) {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
final List<String> fullVersion = iosInfo.systemVersion.split('.');
if (fullVersion.isNotEmpty) {
final int? version = int.tryParse(fullVersion[0]);
if (version != null && version < 10) {
return const Text(
'"getActiveNotifications" returns an empty list before iOS 10',
);
}
}
}

try {
final List<ActiveNotification>? activeNotifications =
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()!
.getActiveNotifications();
await flutterLocalNotificationsPlugin.getActiveNotifications();

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ @implementation FlutterLocalNotificationsPlugin {
NSString *const CANCEL_ALL_METHOD = @"cancelAll";
NSString *const PENDING_NOTIFICATIONS_REQUESTS_METHOD =
@"pendingNotificationRequests";
NSString *const ACTIVE_NOTIFICATIONS_METHOD =
@"getActiveNotifications";
NSString *const GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD =
@"getNotificationAppLaunchDetails";
NSString *const CHANNEL = @"dexterous.com/flutter/local_notifications";
Expand Down Expand Up @@ -77,6 +79,9 @@ @implementation FlutterLocalNotificationsPlugin {
NSString *const PAYLOAD = @"payload";
NSString *const NOTIFICATION_LAUNCHED_APP = @"notificationLaunchedApp";

NSString *const GET_ACTIVE_NOTIFICATIONS_ERROR_CODE = @"GET_ACTIVE_NOTIFICATIONS_ERROR_CODE";
NSString *const GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE = @"iOS version must be 10.0 or newer to use getActiveNotifications";

typedef NS_ENUM(NSInteger, RepeatInterval) {
EveryMinute,
Hourly,
Expand Down Expand Up @@ -166,6 +171,9 @@ - (void)handleMethodCall:(FlutterMethodCall *)call
} else if ([PENDING_NOTIFICATIONS_REQUESTS_METHOD
isEqualToString:call.method]) {
[self pendingNotificationRequests:result];
} else if ([ACTIVE_NOTIFICATIONS_METHOD
isEqualToString:call.method]) {
[self getActiveNotifications:result];
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -200,6 +208,35 @@ - (void)pendingUserNotificationRequests:(FlutterResult _Nonnull)result
}];
}

- (void)activeUserNotificationRequests:(FlutterResult _Nonnull)result
NS_AVAILABLE_IOS(10.0) {
UNUserNotificationCenter *center =
[UNUserNotificationCenter currentNotificationCenter];
[center getDeliveredNotificationsWithCompletionHandler:^(
NSArray<UNNotification *> *_Nonnull notifications) {
NSMutableArray<NSMutableDictionary<NSString *, NSObject *> *>
*activeNotifications =
[[NSMutableArray alloc] initWithCapacity:[notifications count]];
for (UNNotification *notification in notifications) {
NSMutableDictionary *activeNotification =
[[NSMutableDictionary alloc] init];
activeNotification[ID] =
notification.request.content.userInfo[NOTIFICATION_ID];
if (notification.request.content.title != nil) {
activeNotification[TITLE] = notification.request.content.title;
}
if (notification.request.content.body != nil) {
activeNotification[BODY] = notification.request.content.body;
}
if (notification.request.content.userInfo[PAYLOAD] != [NSNull null]) {
activeNotification[PAYLOAD] = notification.request.content.userInfo[PAYLOAD];
}
[activeNotifications addObject:activeNotification];
}
result(activeNotifications);
}];
}

- (void)pendingLocalNotificationRequests:(FlutterResult _Nonnull)result {
NSArray *notifications =
[UIApplication sharedApplication].scheduledLocalNotifications;
Expand Down Expand Up @@ -234,6 +271,17 @@ - (void)pendingNotificationRequests:(FlutterResult _Nonnull)result {
}
}

- (void)getActiveNotifications:(FlutterResult _Nonnull)result {
if (@available(iOS 10.0, *)) {
[self activeUserNotificationRequests:result];
} else {
result([FlutterError
errorWithCode:GET_ACTIVE_NOTIFICATIONS_ERROR_CODE
message:GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE
details:[NSNull null]]);
}
}

- (void)initialize:(NSDictionary *_Nonnull)arguments
result:(FlutterResult _Nonnull)result {
if ([self containsKey:DEFAULT_PRESENT_ALERT forDictionary:arguments]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export 'package:flutter_local_notifications_platform_interface/flutter_local_not
show
SelectNotificationCallback,
PendingNotificationRequest,
ActiveNotification,
RepeatInterval,
NotificationAppLaunchDetails;

Expand All @@ -11,7 +12,6 @@ export 'src/initialization_settings.dart';
export 'src/notification_details.dart';
export 'src/platform_flutter_local_notifications.dart'
hide MethodChannelFlutterLocalNotificationsPlugin;
export 'src/platform_specifics/android/active_notification.dart';
export 'src/platform_specifics/android/bitmap.dart';
export 'src/platform_specifics/android/enums.dart'
hide AndroidBitmapSource, AndroidIconSource, AndroidNotificationSoundSource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,4 +475,8 @@ class FlutterLocalNotificationsPlugin {
/// Returns a list of notifications pending to be delivered/shown.
Future<List<PendingNotificationRequest>> pendingNotificationRequests() =>
FlutterLocalNotificationsPlatform.instance.pendingNotificationRequests();

/// Returns a list of notifications that are already delivered/shown.
Future<List<ActiveNotification>> getActiveNotifications() =>
FlutterLocalNotificationsPlatform.instance.getActiveNotifications();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'package:flutter_local_notifications_platform_interface/flutter_local_not
import 'package:timezone/timezone.dart';

import 'helpers.dart';
import 'platform_specifics/android/active_notification.dart';
import 'platform_specifics/android/enums.dart';
import 'platform_specifics/android/initialization_settings.dart';
import 'platform_specifics/android/method_channel_mappers.dart';
Expand Down Expand Up @@ -65,6 +64,33 @@ class MethodChannelFlutterLocalNotificationsPlugin
.toList() ??
<PendingNotificationRequest>[];
}

/// Returns the list of active notifications shown by the application that
/// haven't been dismissed/removed.
///
/// This method is only applicable to Android 6.0 or newer and will throw an
/// [PlatformException] when called on a device with an incompatible Android
/// version.
///
/// This method is only applicable to iOS 10.0 or newer and will return an
/// empty list when called on a device with an incompatible iOS version.
@override
Future<List<ActiveNotification>> getActiveNotifications() async {
final List<Map<dynamic, dynamic>>? activeNotifications =
await _channel.invokeListMethod('getActiveNotifications');
return activeNotifications
// ignore: always_specify_types
?.map((p) => ActiveNotification(
id: p['id'],
channelId: p['channelId'],
groupKey: p['groupKey'],
title: p['title'],
body: p['body'],
payload: p['payload'],
))
.toList() ??
<ActiveNotification>[];
}
}

/// Android implementation of the local notifications plugin.
Expand Down Expand Up @@ -387,26 +413,6 @@ class AndroidFlutterLocalNotificationsPlugin
Future<void> deleteNotificationChannel(String channelId) =>
_channel.invokeMethod('deleteNotificationChannel', channelId);

/// Returns the list of active notifications shown by the application that
/// haven't been dismissed/removed.
///
/// This method is only applicable to Android 6.0 or newer and will throw an
/// [PlatformException] when called on a device with an incompatible Android
/// version.
Future<List<ActiveNotification>?> getActiveNotifications() async {
final List<Map<dynamic, dynamic>>? activeNotifications =
await _channel.invokeListMethod('getActiveNotifications');
return activeNotifications
// ignore: always_specify_types
?.map((a) => ActiveNotification(
a['id'],
a['channelId'],
a['title'],
a['body'],
))
.toList();
}

/// Returns the list of all notification channels.
///
/// This method is only applicable on Android 8.0 or newer. On older versions,
Expand Down

This file was deleted.

Loading

0 comments on commit a7c8348

Please sign in to comment.