Skip to content

Commit

Permalink
[flutter_local_notifications] add ability to manage notification chan…
Browse files Browse the repository at this point in the history
…nel groups (#870)

* add ability to manage notification channel groups

* update code for deleting notification channels to use NotificationManagerCompat that includes API level check already

* bump plugin version to 3.0.1

* updat PR template so that the note for prefixing PR title is a comment

* Revert "updat PR template so that the note for prefixing PR title is a comment"

This reverts commit 82bb33b.

* fix API docs

* retrigger checks

* add missing word to changelog entry
  • Loading branch information
MaikuB authored Oct 25, 2020
1 parent b8ed130 commit 1af3ef4
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 9 deletions.
4 changes: 4 additions & 0 deletions flutter_local_notifications/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# [3.0.1]

* [Android] Added the `createNotificationChannelGroup` and `deleteNotificationChannelGroup` methods to the `AndroidFluttterLocalNotificationsPlugin` class that can be used to create and delete notification channel groups. The optional `groupId` parameter has been added to the `AndroidNotificationChannel` class that can be used to associated notification channels to a particular group. Example app has been updated to include code snippets for this.

# [3.0.0+1]

* [iOS] Fixed issue [865](https://github.com/MaikuB/flutter_local_notifications/issues/865) where notifications with no title weren't behaving properly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
Expand Down Expand Up @@ -33,6 +34,7 @@
import com.dexterous.flutterlocalnotifications.models.MessageDetails;
import com.dexterous.flutterlocalnotifications.models.NotificationChannelAction;
import com.dexterous.flutterlocalnotifications.models.NotificationChannelDetails;
import com.dexterous.flutterlocalnotifications.models.NotificationChannelGroupDetails;
import com.dexterous.flutterlocalnotifications.models.NotificationDetails;
import com.dexterous.flutterlocalnotifications.models.PersonDetails;
import com.dexterous.flutterlocalnotifications.models.ScheduledNotificationRepeatFrequency;
Expand Down Expand Up @@ -85,6 +87,8 @@ public class FlutterLocalNotificationsPlugin implements MethodCallHandler, Plugi
private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION";
private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications";
private static final String INITIALIZE_METHOD = "initialize";
private static final String CREATE_NOTIFICATION_CHANNEL_GROUP_METHOD = "createNotificationChannelGroup";
private static final String DELETE_NOTIFICATION_CHANNEL_GROUP_METHOD = "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";
Expand Down Expand Up @@ -705,6 +709,7 @@ private static void setupNotificationChannel(Context context, NotificationChanne
if ((notificationChannel == null && (notificationChannelDetails.channelAction == null || notificationChannelDetails.channelAction == NotificationChannelAction.CreateIfNotExists)) || (notificationChannel != null && notificationChannelDetails.channelAction == NotificationChannelAction.Update)) {
notificationChannel = new NotificationChannel(notificationChannelDetails.id, notificationChannelDetails.name, notificationChannelDetails.importance);
notificationChannel.setDescription(notificationChannelDetails.description);
notificationChannel.setGroup(notificationChannelDetails.groupId);
if (notificationChannelDetails.playSound) {
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
Uri uri = retrieveSoundResourceUri(context, notificationChannelDetails.sound, notificationChannelDetails.soundSource);
Expand Down Expand Up @@ -809,7 +814,7 @@ static String getNextFireDateMatchingDateTimeComponents(NotificationDetails noti
ZonedDateTime scheduledDateTime = ZonedDateTime.of(LocalDateTime.parse(notificationDetails.scheduledDateTime), zoneId);
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZonedDateTime nextFireDate = ZonedDateTime.of(now.getYear(), now.getMonthValue(), now.getDayOfMonth(), scheduledDateTime.getHour(), scheduledDateTime.getMinute(), scheduledDateTime.getSecond(), scheduledDateTime.getNano(), zoneId);
while(nextFireDate.isBefore(now)) {
while (nextFireDate.isBefore(now)) {
// adjust to be a date in the future that matches the time
nextFireDate = nextFireDate.plusDays(1);
}
Expand Down Expand Up @@ -930,6 +935,12 @@ public void onMethodCall(MethodCall call, Result result) {
case PENDING_NOTIFICATION_REQUESTS_METHOD:
pendingNotificationRequests(result);
break;
case CREATE_NOTIFICATION_CHANNEL_GROUP_METHOD:
createNotificationChannelGroup(call, result);
break;
case DELETE_NOTIFICATION_CHANNEL_GROUP_METHOD:
deleteNotificationChannelGroup(call, result);
break;
case CREATE_NOTIFICATION_CHANNEL_METHOD:
createNotificationChannel(call, result);
break;
Expand Down Expand Up @@ -988,7 +999,7 @@ private void zonedSchedule(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
if(notificationDetails.matchDateTimeComponents != null) {
if (notificationDetails.matchDateTimeComponents != null) {
notificationDetails.scheduledDateTime = getNextFireDateMatchingDateTimeComponents(notificationDetails);
}
zonedScheduleNotification(applicationContext, notificationDetails, true);
Expand Down Expand Up @@ -1142,6 +1153,27 @@ private Boolean sendNotificationPayloadMessage(Intent intent) {
return false;
}

private void createNotificationChannelGroup(MethodCall call, Result result) {
if (VERSION.SDK_INT >= VERSION_CODES.O) {
Map<String, Object> arguments = call.arguments();
NotificationChannelGroupDetails notificationChannelGroupDetails = NotificationChannelGroupDetails.from(arguments);
NotificationManagerCompat notificationManagerCompat = getNotificationManager(applicationContext);
NotificationChannelGroup notificationChannelGroup = new NotificationChannelGroup(notificationChannelGroupDetails.id, notificationChannelGroupDetails.name);
if (VERSION.SDK_INT >= VERSION_CODES.P) {
notificationChannelGroup.setDescription(notificationChannelGroupDetails.description);
}
notificationManagerCompat.createNotificationChannelGroup(notificationChannelGroup);
}
result.success(null);
}

private void deleteNotificationChannelGroup(MethodCall call, Result result) {
NotificationManagerCompat notificationManagerCompat = getNotificationManager(applicationContext);
String groupId = call.arguments();
notificationManagerCompat.deleteNotificationChannelGroup(groupId);
result.success(null);
}

private void createNotificationChannel(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
NotificationChannelDetails notificationChannelDetails = NotificationChannelDetails.from(arguments);
Expand All @@ -1150,12 +1182,10 @@ private void createNotificationChannel(MethodCall call, Result result) {
}

private void deleteNotificationChannel(MethodCall call, Result result) {
if (VERSION.SDK_INT >= VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
String channelId = call.arguments();
notificationManager.deleteNotificationChannel(channelId);
result.success(null);
}
NotificationManagerCompat notificationManagerCompat = getNotificationManager(applicationContext);
String channelId = call.arguments();
notificationManagerCompat.deleteNotificationChannel(channelId);
result.success(null);
}

private void getActiveNotifications(Result result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class NotificationChannelDetails {
private static final String ID = "id";
private static final String NAME = "name";
private static final String DESCRIPTION = "description";
private static final String GROUP_ID = "groupId";
private static final String SHOW_BADGE = "showBadge";
private static final String IMPORTANCE = "importance";
private static final String PLAY_SOUND = "playSound";
Expand All @@ -27,6 +28,7 @@ public class NotificationChannelDetails {
public String id;
public String name;
public String description;
public String groupId;
public Boolean showBadge;
public Integer importance;
public Boolean playSound;
Expand All @@ -44,6 +46,7 @@ public static NotificationChannelDetails from(Map<String, Object> arguments) {
notificationChannel.id = (String) arguments.get(ID);
notificationChannel.name = (String) arguments.get(NAME);
notificationChannel.description = (String) arguments.get(DESCRIPTION);
notificationChannel.groupId = (String) arguments.get(GROUP_ID);
notificationChannel.importance = (Integer) arguments.get(IMPORTANCE);
notificationChannel.showBadge = (Boolean) arguments.get(SHOW_BADGE);
notificationChannel.channelAction = NotificationChannelAction.values()[(Integer) arguments.get(CHANNEL_ACTION)];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.dexterous.flutterlocalnotifications.models;

import java.util.Map;

public class NotificationChannelGroupDetails {
private static final String ID = "id";
private static final String NAME = "name";
private static final String DESCRIPTION = "description";


public String id;
public String name;
public String description;

public static NotificationChannelGroupDetails from(Map<String, Object> arguments) {
NotificationChannelGroupDetails notificationChannelGroupDetails = new NotificationChannelGroupDetails();
notificationChannelGroupDetails.id = (String) arguments.get(ID);
notificationChannelGroupDetails.name = (String) arguments.get(NAME);
notificationChannelGroupDetails.description = (String) arguments.get(DESCRIPTION);
return notificationChannelGroupDetails;
}
}
82 changes: 82 additions & 0 deletions flutter_local_notifications/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,18 @@ class _HomePageState extends State<HomePage> {
await _showFullScreenNotification();
},
),
PaddedRaisedButton(
buttonText: 'Create grouped notification channels',
onPressed: () async {
await _createNotificationChannelGroup();
},
),
PaddedRaisedButton(
buttonText: 'Delete notification channel group',
onPressed: () async {
await _deleteNotificationChannelGroup();
},
),
PaddedRaisedButton(
buttonText: 'Create notification channel',
onPressed: () async {
Expand Down Expand Up @@ -1288,6 +1300,76 @@ class _HomePageState extends State<HomePage> {
notificationDetails);
}

Future<void> _createNotificationChannelGroup() async {
const String channelGroupId = 'your channel group id';
// create the group first
const AndroidNotificationChannelGroup androidNotificationChannelGroup =
AndroidNotificationChannelGroup(
channelGroupId, 'your channel group name',
description: 'your channel group description');
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
.createNotificationChannelGroup(androidNotificationChannelGroup);

// create channels associated with the group
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
.createNotificationChannel(const AndroidNotificationChannel(
'grouped channel id 1',
'grouped channel name 1',
'grouped channel description 1',
groupId: channelGroupId));

await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
.createNotificationChannel(const AndroidNotificationChannel(
'grouped channel id 2',
'grouped channel name 2',
'grouped channel description 2',
groupId: channelGroupId));

await showDialog<void>(
context: context,
builder: (BuildContext context) => AlertDialog(
content: Text('Channel group with name '
'${androidNotificationChannelGroup.name} created'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
));
}

Future<void> _deleteNotificationChannelGroup() async {
const String channelGroupId = 'your channel group id';
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.deleteNotificationChannelGroup(channelGroupId);

await showDialog<void>(
context: context,
builder: (BuildContext context) => AlertDialog(
content: const Text('Channel group with id $channelGroupId deleted'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
),
);
}

Future<void> _createNotificationChannel() async {
const AndroidNotificationChannel androidNotificationChannel =
AndroidNotificationChannel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export 'src/platform_specifics/android/icon.dart' hide AndroidIcon;
export 'src/platform_specifics/android/initialization_settings.dart';
export 'src/platform_specifics/android/message.dart';
export 'src/platform_specifics/android/notification_channel.dart';
export 'src/platform_specifics/android/notification_channel_group.dart';
export 'src/platform_specifics/android/notification_details.dart';
export 'src/platform_specifics/android/notification_sound.dart';
export 'src/platform_specifics/android/person.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'platform_specifics/android/active_notification.dart';
import 'platform_specifics/android/initialization_settings.dart';
import 'platform_specifics/android/method_channel_mappers.dart';
import 'platform_specifics/android/notification_channel.dart';
import 'platform_specifics/android/notification_channel_group.dart';
import 'platform_specifics/android/notification_details.dart';
import 'platform_specifics/ios/enums.dart';
import 'platform_specifics/ios/initialization_settings.dart';
Expand Down Expand Up @@ -243,6 +244,21 @@ class AndroidFlutterLocalNotificationsPlugin
});
}

/// Creates a notification channel group.
///
/// This method is only applicable to Android versions 8.0 or newer.
Future<void> createNotificationChannelGroup(
AndroidNotificationChannelGroup notificationChannelGroup) =>
_channel.invokeMethod(
'createNotificationChannelGroup', notificationChannelGroup.toMap());

/// Deletes the notification channel group with the specified [groupId]
/// as well as all of the channels belonging to the group.
///
/// This method is only applicable to Android versions 8.0 or newer.
Future<void> deleteNotificationChannelGroup(String groupId) =>
_channel.invokeMethod('deleteNotificationChannelGroup', groupId);

/// Creates a notification channel.
///
/// This method is only applicable to Android versions 8.0 or newer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'icon.dart';
import 'initialization_settings.dart';
import 'message.dart';
import 'notification_channel.dart';
import 'notification_channel_group.dart';
import 'notification_details.dart';
import 'notification_sound.dart';
import 'person.dart';
Expand All @@ -29,11 +30,21 @@ extension MessageMapper on Message {
};
}

extension AndroidNotificationChannelGroupMapper
on AndroidNotificationChannelGroup {
Map<String, Object> toMap() => <String, Object>{
'id': id,
'name': name,
'description': description,
};
}

extension AndroidNotificationChannelMapper on AndroidNotificationChannel {
Map<String, Object> toMap() => <String, Object>{
'id': id,
'name': name,
'description': description,
'groupId': groupId,
'showBadge': showBadge,
'importance': importance.value,
'playSound': playSound,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import 'dart:ui';
import 'enums.dart';
import 'notification_sound.dart';

/// Settings for Android notification channels.
class AndroidNotificationChannel {
const AndroidNotificationChannel(
this.id,
this.name,
this.description, {
this.groupId,
this.importance = Importance.defaultImportance,
this.playSound = true,
this.sound,
Expand All @@ -28,6 +30,9 @@ class AndroidNotificationChannel {
/// The channel's description.
final String description;

/// The id of the group that the channel belongs to.
final String groupId;

/// The importance of the notification.
final Importance importance;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// A group of related Android notification channels.
class AndroidNotificationChannelGroup {
const AndroidNotificationChannelGroup(
this.id,
this.name, {
this.description,
});

/// The id of this group.
final String id;

/// The name of this group.
final String name;

/// The description of this group.
///
/// Only applicable to Android 9.0 or newer.
final String description;
}
2 changes: 1 addition & 1 deletion flutter_local_notifications/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_local_notifications
description: A cross platform plugin for displaying and scheduling local notifications for Flutter applications with the ability to customise for each platform.
version: 3.0.0+1
version: 3.0.1
homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications

dependencies:
Expand Down
Loading

0 comments on commit 1af3ef4

Please sign in to comment.