-
Notifications
You must be signed in to change notification settings - Fork 306
notif: Support migration of Android notification channels #981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
notif: Support migration of Android notification channels #981
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Small comments below, and marking for Greg's review.
test/model/binding.dart
Outdated
/// Consumes the log of sequence of calls made to [getNotificationChannels], | ||
/// [deleteNotificationChannel] and [createNotificationChannel]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"log of sequence of calls" -> "log of calls"?
if (channel!.id == kChannelId) { | ||
found = true; | ||
} else { | ||
await _androidHost.deleteNotificationChannel(channel.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like it would be helpful to mention this in the dartdoc; something like:
/// Create our notification channel, if it doesn't already exist.
///
/// Deletes obsolete channels, if present, from old versions of the app.
lib/notifications/display.dart
Outdated
// TODO(?) at launch make sure this channel-id doesn't collide with the one | ||
// present in zulip-mobile. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More accurately, I think, the collision to avoid is with a channel ID that's active in the installed app the user will be upgrading from. In theory, that could be different from the one zulip-mobile has in main
, right, because the user could be upgrading from a very old zulip-mobile straight to zulip-flutter?
I see in main
zulip-mobile has "messages-3". There's a handy list of previous values in a code comment on it:
/** The channel ID we use for our one notification channel, which we use for all notifications. */
// Previous values: "default", "messages-1", (alpha-only: "messages-2")
val CHANNEL_ID = "messages-3"
which…hmm, that list does include "messages-1". I'm not sure how old that one is, and how many of our users will still have that as their active channel.
Rather than tracking that down, maybe simplest to be conservative and just say something like
// TODO(launch) check this doesn't match zulip-mobile's current or previous channel IDs
8afb983
to
c85adc9
Compare
Thanks for the review @chrisbobbe! Pushed a new revision PTAL. |
Thanks! LGTM, over to @gnprice. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @rajveermalviya, and thanks @chrisbobbe for the previous review!
Generally this looks great. Various comments below.
test/model/binding.dart
Outdated
} | ||
|
||
@override | ||
Future<void> deleteNotificationChannel(String channelId) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: avoid the intermediate state with
// TODO: implement deleteNotificationChannel
throw UnimplementedError();
by adding the implementation here in the same commit that adds the binding
.map { NotificationChannel( | ||
it.id, | ||
it.importance.toLong(), | ||
it.name?.toString(), | ||
it.shouldShowLights() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use named parameters for these — otherwise it's impossible to tell locally if some of the parameters have been accidentally swapped
test/model/binding.dart
Outdated
Future<List<NotificationChannel?>> getNotificationChannels() async { | ||
_channelMethodCallLogs.add('getNotificationChannels'); | ||
return _createdChannels.toList(growable: false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This simulation leaves out one important aspect of how getNotificationChannels
can behave: it can return channels that weren't created by this run of the app, but by some previous run (possibly years in the past).
If the tests you're currently writing don't need that feature, then it's fine to leave it out. But let's leave a comment here mentioning that gap. (Partly that serves as an invitation to a future developer to add that feature, if they find themself needing it.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, what the tests effectively do is call createNotificationChannel
in order to simulate a channel having been created in the distant past.
That works fine. But this approach changes the semantics of _createdChannels
— particularly because deleteNotificationChannel
now removes channels from that list. Instead of a log of what was created, it's really serving now as a list of the channels that currently exist. The name, and the "take"-based API which is suited for a log, are no longer a fit.
So let's have a prep commit that replaces takeCreatedChannels
with a getter like:
Iterable<NotificationChannel> get createdChannels => _createdChannels;
Then that's well adapted for the direction it gets extended in the rest of this PR.
test/model/binding.dart
Outdated
/// [deleteNotificationChannel] and [createNotificationChannel]. | ||
/// | ||
/// Returns a list of function names in the order they were invoked. | ||
List<String> takeChannelMethodCallLogs() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "log" singular, since this is really just one log
lib/notifications/display.dart
Outdated
// TODO(launch) check this doesn't match zulip-mobile's current or previous | ||
// channel IDs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: in a TODO comment (or other labeled comment), indent any continuation lines to keep them grouped under the heading:
// TODO(launch) check this doesn't match zulip-mobile's current or previous | |
// channel IDs | |
// TODO(launch) check this doesn't match zulip-mobile's current or previous | |
// channel IDs |
test/notifications/display_test.dart
Outdated
test('channel is not recreated if one with same id already exists', () async { | ||
await NotificationChannelManager.ensureChannel(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like these tests affect the state in the binding, and then leak that state. To fix that, they should start with addTearDown(testBinding.reset)
(like the existing smoke test does via init
).
test/notifications/display_test.dart
Outdated
await NotificationChannelManager.ensureChannel(); | ||
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) | ||
.deepEquals(['getNotificationChannels', 'createNotificationChannel']); | ||
|
||
await NotificationChannelManager.ensureChannel(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the first ensureChannel
here is to simulate a channel having gotten created in the past, and then the second is the call that we're testing the behavior of.
I think it'd be cleaner to do the setup more directly, without ensureChannel
. Using ensureChannel
will only produce a channel that's exactly the way the current code sets it up; this breaks down in the next test case, where we want to simulate some previous version of the app having set it up, and it also breaks down with the possibility that the user may have altered the settings on the channel. (If they have, we should leave the existing channel in place; only the ID counts for saying it's the same channel.)
test/notifications/display_test.dart
Outdated
check(testBinding.androidNotificationHost.takeChannelMethodCallLogs()) | ||
.deepEquals(['getNotificationChannels']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm generally not a fan of having "get" calls in a log that a test checks like this — it feels too far into testing the implementation details (cf https://zulip.readthedocs.io/en/stable/testing/philosophy.html#integration-testing-or-unit-testing ).
(We do routinely include the equivalent in our API tests — we check what HTTP requests the app made to the simulated server. But because these queries are local to the device, I'd like to try to treat them more as internal.)
Can we leave these out of the log, and have the tests just look at what the code does with the information? I.e., the create
and delete
calls.
c85adc9
to
b3a5b2a
Compare
Needed for zulip#340, when updating notification channels to use a custom notification sound.
b3a5b2a
to
00deaa6
Compare
Thanks for the reviews @chrisbobbe and @gnprice! |
Thanks for the revision! All looks good — merging. |
(Split from #717)
Needed for #340, when updating notification channels to use a custom notification sound.