-
Notifications
You must be signed in to change notification settings - Fork 857
Splitting notifications into separate creation and displaying passes #4235
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
Splitting notifications into separate creation and displaying passes #4235
Conversation
| override val isRedacted: Boolean = false | ||
| ) : NotifiableEvent { | ||
|
|
||
| override var hasBeenDisplayed = 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.
no longer needed as we diff the eventList and renderedEventsList
| private val autoAcceptInvites: AutoAcceptInvites | ||
| ) { | ||
|
|
||
| fun process(eventList: List<NotifiableEvent>, currentRoomId: String?, renderedEventsList: List<ProcessedEvent>): List<ProcessedEvent> { |
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.
a simplified version of https://github.com/vector-im/element-android/pull/4235/files#diff-c1e2253b1ef80b85a77d664922bf07c861d56dc472e8fbc51b8587ec264269daL255
- no longer mutates the list which allows us to reduce the scope of the synchronised block
- handles differences in the
renderedEventsListandeventListkeeping the notifications in sync
|
|
||
| return bitmapLoader.getRoomBitmap(roomAvatarPath) | ||
| if (renderedEventsList == eventsToRender) { | ||
| Timber.d("Skipping notification update due to event list not changing") |
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.
redundant notification updates can now avoid triggering any updates
| private val notificationUtils: NotificationUtils | ||
| ) { | ||
|
|
||
| fun createRoomMessage(events: List<NotifiableMessageEvent>, roomId: String, userDisplayName: String, userAvatarUrl: String?): RoomNotification.Message { |
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.
| private val notificationUtils: NotificationUtils | ||
| ) { | ||
|
|
||
| fun createSummaryNotification(roomNotifications: List<RoomNotification.Message.Meta>, |
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.
| eventsToProcess: List<ProcessedEvent>) { | ||
| val (roomEvents, simpleEvents, invitationEvents) = eventsToProcess.groupByType() | ||
| with(notificationFactory) { | ||
| val roomNotifications = roomEvents.toNotifications(myUserDisplayName, myUserAvatarUrl) |
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 is the main change of the PR
all the notifications are created and then displayed in a single pass, the aim being to reduce the time between the notification message queue being updated for the group and child notifications (hopefully eliminating the chance of the system displaying the group as a separate notification 🤞 )
| } | ||
| val shouldUpdate = removeAll { it is NotifiableMessageEvent && it.roomId == roomId } | ||
| if (shouldUpdate) { | ||
| notificationUtils.cancelNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_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.
I have a sneaky suspicion this also might have been contributing to the duplicate notifications where we were eagerly cancelling whilst a refreshNotificationDrawerBg may have been running
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.
good point
| eventList.addAll(it.onlyKeptEvents()) | ||
| } | ||
| // notice that we can get bit out of sync with actual display but not a big issue | ||
| firstTime = 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.
firstTime is no longer needed, the diffing between renderedEventsList and eventList handles this for us
ba6e019 to
8429807
Compare
…e handled - also puts in the basis for a separate notification refreshing implementation
- updates the logic to track when events are removed as a way for the notifications to remove themselves, null events mean they've been removed
- also handles when the event diff means the notifications should be removed
…nto a NotificationRender - extract the displaying into its own class to avoid leaking the entire notificationutils - cancel/display notification actions are completely driven by the event or abscense of event from the eventList - attempts to avoid redundant render passes by checking if the eventList has changed since the last render
- the renderer's responsibility it handling events
…r source of truth - when events have finished being displayed they should be removed from the eventList via notification delete actions
…o empty due to only containing removals
…l notifications - adds some comments to explain the positioning
- this allows us to only synchronise of the event list modifications rather than the entire notification creation/rendering which should in turn reduce some of our ANRs #4214
…ed event id - this state will be used to diff the currently rendered events against the new ones
… allow us to dismiss notifications from removed events
…cting the processed type when creating the notifications
…as our notification source of truth
799667f to
5029937
Compare
| is RoomNotification.Message -> if (useCompleteNotificationFormat) { | ||
| Timber.d("Updating room messages notification ${wrapper.meta.roomId}") | ||
| wrapper.shortcutInfo?.let { | ||
| ShortcutManagerCompat.pushDynamicShortcut(appContext, 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.
Shortcut adding maintained, although now we're only doing it once per room rather than for every message in the room
https://github.com/vector-im/element-android/blob/develop/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt#L356
we're also able to easily remove the shortcuts when the notification is removed (if needed)
| } | ||
|
|
||
| val largeBitmap = getRoomBitmap(events) | ||
| val shortcutInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
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.
|
|
||
| private fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) { | ||
| events.forEach { event -> | ||
| val senderPerson = if (event.outGoingMessage) { |
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.
bmarty
left a comment
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 is an amazing work, thanks!
I have a few very minor remarks.
I did not run the code yet. Is it the last PR about your notification rework?
| import timber.log.Timber | ||
| import javax.inject.Inject | ||
|
|
||
| class NotificationDisplayer @Inject constructor(context: Context) { |
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 like this simple class 👍
| * we keep track of them in order to know which events have been removed from the eventList | ||
| * allowing us to cancel any notifications previous displayed by now removed events | ||
| */ | ||
| private var renderedEventsList = emptyList<Pair<ProcessedType, NotifiableEvent>>() |
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.
You could use the typealias ProcessedEvent here (or the data class :)).
| } | ||
| val shouldUpdate = removeAll { it is NotifiableMessageEvent && it.roomId == roomId } | ||
| if (shouldUpdate) { | ||
| notificationUtils.cancelNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_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.
good point
| } | ||
| } | ||
|
|
||
| fun displayDiagnosticNotification() { |
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.
Was it not used here? I guess yes
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.
^^^ correct, unused
|
|
||
| package im.vector.app.features.notifications | ||
|
|
||
| typealias ProcessedEvent = Pair<ProcessedType, NotifiableEvent> |
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 like typealias, but I do not like .first and .second, which are meaning less. I would prefer to have a simple data class in this case. WDYT?
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.
agreed, will switch to a data class 💯
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.
done! f5de7f7
| import androidx.core.content.pm.ShortcutInfoCompat | ||
| import javax.inject.Inject | ||
|
|
||
| private typealias ProcessedMessageEvent = Pair<ProcessedType, NotifiableMessageEvent> |
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.
same remark about typealias...
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.
will replace with a data class 👍
|
|
||
| private fun NotifiableMessageEvent.canNotBeDisplayed() = isRedacted | ||
|
|
||
| fun List<Pair<ProcessedType, InviteNotifiableEvent>>.toNotifications(myUserId: String): List<OneShotNotification> { |
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: Maybe also use @JvmName() here, even if it's not strictly necessary?
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.
good idea 👍
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.
also done as part of f5de7f7
thanks 😄 there's one more tiny PR around changing the notification ids to avoid |
f9e6718 to
34684cc
Compare
…assing around the process notificatiable events - also includes @JvmName on all conflicting extensions for consistency
34684cc to
f5de7f7
Compare
| ) { | ||
|
|
||
| fun process(eventList: List<NotifiableEvent>, currentRoomId: String?, renderedEventsList: List<ProcessedEvent>): List<ProcessedEvent> { | ||
| fun process(eventList: List<NotifiableEvent>, currentRoomId: String?, renderedEventsList: ProcessedEvents): ProcessedEvents { |
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: I know this is not new, but maybe the word List is not necessary in the parameter names. So instead of having renderedEventsList, we could simply have renderedEvents. The plural form is generally enough (to me) to understand that this is not a simple object. This is my point of view, I'm open to different ones :)
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.
Also we can see some inconsistency in the naming convention between eventList and renderedEventsList :)
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.
will fix whilst I'm in the area 👍
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 is OK to me to merge this PR into the branch |
correct! I'll raise the entire feature branch as a PR against develop |
bmarty
left a comment
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.
LGTM, please merge the PR whenever you want!
| fun process(eventList: List<NotifiableEvent>, currentRoomId: String?, renderedEventsList: ProcessedEvents): ProcessedEvents { | ||
| val processedEventList = eventList.map { | ||
| fun process(queuedEvents: List<NotifiableEvent>, currentRoomId: String?, renderedEvents: ProcessedEvents): ProcessedEvents { | ||
| val processedEventList = queuedEvents.map { |
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.
Please do not change it, but there is still a List suffix in processedEventList (I know I am a pain sometimes 😆 )
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.
ah, missed that 👍
and I'm happy to fix it! will only take a few seconds (maybe longer for the CI 😅 )
0fe1b69
3e10145 to
0fe1b69
Compare
Previously opened as #4185 and #4220
This entire change is the result of extracting, breaking down and unit testing 1 function NotificationDrawerManager.refreshNotificationDrawerBg
There are 3 main objectives/changes to the notification design
system UI message queue, which in turn should address our double notification problem 🤞 Doubled notifications #4152
queueEvents(previously calledeventList) is the sole source of truth, we now simply render it as android notifications, when a notification is dismissed it triggers a delete action which causes the event to be reflected in theeventList, which in turn renders the newly missing event as a notification removal.cancel -> showflows, we only show or cancel event changesRoomGroupMessageCreator&SummaryGroupMessageCreatorare not particularly friendly to unit tests due to calling into android builders (although we could abstract that and test...)Tentatively fixes the double notifications Doubled notifications #4152
Fixes notification related ANR [ANR] NotificationDrawerManager.setCurrentRoom #4214
Fixes the serialised event list becoming out of sync with the currently rendered notifications, eg receiving multiple room invites
event listandcurrently rendered eventsdiffing, this allows us to remove out of sync notifications