Skip to content

Commit 8afb983

Browse files
notif: Support migration of Android notification channels
Needed for #340, when updating notification channels to use a custom notification sound.
1 parent 6bf675b commit 8afb983

File tree

3 files changed

+98
-12
lines changed

3 files changed

+98
-12
lines changed

lib/notifications/display.dart

+27-6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ AndroidNotificationHostApi get _androidHost => ZulipBinding.instance.androidNoti
2525

2626
/// Service for configuring our Android "notification channel".
2727
class NotificationChannelManager {
28+
/// The channel ID we use for our one notification channel, which we use for
29+
/// all notifications.
30+
// TODO(?) at launch make sure this channel-id doesn't collide with the one
31+
// present in zulip-mobile.
2832
@visibleForTesting
2933
static const kChannelId = 'messages-1';
3034

@@ -52,11 +56,28 @@ class NotificationChannelManager {
5256
// settings for the channel -- like "override Do Not Disturb", or "use
5357
// a different sound", or "don't pop on screen" -- their changes get
5458
// reset. So this has to be done sparingly.
55-
//
56-
// If we do this, we should also look for any channel with the old
57-
// channel ID and delete it. See zulip-mobile's `createNotificationChannel`
58-
// in android/app/src/main/java/com/zulipmobile/notifications/NotificationChannelManager.kt .
59-
static Future<void> _ensureChannel() async {
59+
@visibleForTesting
60+
static Future<void> ensureChannel() async {
61+
// See if our current-version channel already exists; delete any obsolete
62+
// previous channels.
63+
var found = false;
64+
final channels = await _androidHost.getNotificationChannels();
65+
for (final channel in channels) {
66+
assert(channel != null); // TODO(#942)
67+
if (channel!.id == kChannelId) {
68+
found = true;
69+
} else {
70+
await _androidHost.deleteNotificationChannel(channel.id);
71+
}
72+
}
73+
74+
if (found) {
75+
// The channel already exists; nothing to do.
76+
return;
77+
}
78+
79+
// The channel doesn't exist. Create it.
80+
6081
await _androidHost.createNotificationChannel(NotificationChannel(
6182
id: kChannelId,
6283
name: 'Messages', // TODO(i18n)
@@ -81,7 +102,7 @@ class NotificationDisplayManager {
81102
if (launchDetails?.didNotificationLaunchApp ?? false) {
82103
_handleNotificationAppLaunch(launchDetails!.notificationResponse);
83104
}
84-
await NotificationChannelManager._ensureChannel();
105+
await NotificationChannelManager.ensureChannel();
85106
}
86107

87108
static void onFcmMessage(FcmMessage data, Map<String, dynamic> dataJson) {

test/model/binding.dart

+18-6
Original file line numberDiff line numberDiff line change
@@ -554,20 +554,32 @@ class FakeAndroidNotificationHostApi implements AndroidNotificationHostApi {
554554
}
555555
List<NotificationChannel> _createdChannels = [];
556556

557+
/// Consumes the log of sequence of calls made to [getNotificationChannels],
558+
/// [deleteNotificationChannel] and [createNotificationChannel].
559+
///
560+
/// Returns a list of function names in the order they were invoked.
561+
List<String> takeChannelMethodCallLogs() {
562+
final result = _channelMethodCallLogs;
563+
_channelMethodCallLogs = [];
564+
return result;
565+
}
566+
List<String> _channelMethodCallLogs = [];
567+
557568
@override
558-
Future<List<NotificationChannel?>> getNotificationChannels() {
559-
// TODO: implement getNotificationChannels
560-
throw UnimplementedError();
569+
Future<List<NotificationChannel?>> getNotificationChannels() async {
570+
_channelMethodCallLogs.add('getNotificationChannels');
571+
return _createdChannels.toList(growable: false);
561572
}
562573

563574
@override
564-
Future<void> deleteNotificationChannel(String channelId) {
565-
// TODO: implement deleteNotificationChannel
566-
throw UnimplementedError();
575+
Future<void> deleteNotificationChannel(String channelId) async {
576+
_channelMethodCallLogs.add('deleteNotificationChannel');
577+
_createdChannels.removeWhere((e) => e.id == channelId);
567578
}
568579

569580
@override
570581
Future<void> createNotificationChannel(NotificationChannel channel) async {
582+
_channelMethodCallLogs.add('createNotificationChannel');
571583
_createdChannels.add(channel);
572584
}
573585

test/notifications/display_test.dart

+53
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,59 @@ void main() {
131131
NotificationChannelManager.kVibrationPattern)
132132
;
133133
});
134+
135+
test('channel is not recreated if one with same id already exists', () async {
136+
await NotificationChannelManager.ensureChannel();
137+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
138+
.deepEquals(['getNotificationChannels', 'createNotificationChannel']);
139+
140+
await NotificationChannelManager.ensureChannel();
141+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
142+
.deepEquals(['getNotificationChannels']);
143+
144+
check(testBinding.androidNotificationHost.takeCreatedChannels()).single
145+
..id.equals(NotificationChannelManager.kChannelId)
146+
..name.equals('Messages')
147+
..importance.equals(NotificationImportance.high)
148+
..lightsEnabled.equals(true)
149+
..vibrationPattern.isNotNull().deepEquals(
150+
NotificationChannelManager.kVibrationPattern);
151+
});
152+
153+
test('obsolete channels are removed', () async {
154+
await NotificationChannelManager.ensureChannel();
155+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
156+
.deepEquals(['getNotificationChannels', 'createNotificationChannel']);
157+
158+
await testBinding.androidNotificationHost.createNotificationChannel(NotificationChannel(
159+
id: 'obsolete-1',
160+
name: 'Obsolete 1',
161+
importance: NotificationImportance.high,
162+
lightsEnabled: true,
163+
vibrationPattern: NotificationChannelManager.kVibrationPattern));
164+
await testBinding.androidNotificationHost.createNotificationChannel(NotificationChannel(
165+
id: 'obsolete-2',
166+
name: 'Obsolete 2',
167+
importance: NotificationImportance.high,
168+
lightsEnabled: true,
169+
vibrationPattern: NotificationChannelManager.kVibrationPattern));
170+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
171+
.deepEquals(['createNotificationChannel', 'createNotificationChannel']);
172+
173+
await NotificationChannelManager.ensureChannel();
174+
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs())
175+
.deepEquals([
176+
'getNotificationChannels',
177+
'deleteNotificationChannel',
178+
'deleteNotificationChannel']);
179+
check(testBinding.androidNotificationHost.takeCreatedChannels()).single
180+
..id.equals(NotificationChannelManager.kChannelId)
181+
..name.equals('Messages')
182+
..importance.equals(NotificationImportance.high)
183+
..lightsEnabled.equals(true)
184+
..vibrationPattern.isNotNull().deepEquals(
185+
NotificationChannelManager.kVibrationPattern);
186+
});
134187
});
135188

136189
group('NotificationDisplayManager show', () {

0 commit comments

Comments
 (0)