Skip to content
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 #4185

Conversation

ouchadam
Copy link
Contributor

@ouchadam ouchadam commented Oct 7, 2021

Well this ballooned in size...

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

  1. The notification flow becomes a 2 pass system. All notifications are created and then all notifications are notified/cancelled, instead of looping through all the events notifying/cancelling as we go.
  • This has the benefit of reducing the time between posting individual message and summary group notification changes to the
    system UI message queue, which in turn should address our double notification problem 🤞 Doubled notifications #4152
  1. The eventList 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 the eventList, which in turn renders the newly missing event as a notification removal.
  • This avoids redundant notification cancel -> show flows, we only show or cancel event changes
  1. Testable, immutable and simpler to reason, we create all the notifications and then display all the notifications
  • Creates small classes with single responsibilities, tested where possible. The notification creation helpers RoomGroupMessageCreator & SummaryGroupMessageCreator are not particularly friendly to unit tests due to calling into android builders (although we could abstract that and test...)

private val autoAcceptInvites: AutoAcceptInvites
) {

fun modifyAndProcess(eventList: MutableList<NotifiableEvent>, currentRoomId: String?): ProcessedNotificationEvents {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) {

fun createRoomMessage(events: List<NotifiableMessageEvent>, roomId: String, userDisplayName: String, userAvatarUrl: String?): RoomNotification.Message {
val firstKnownRoomEvent = events[0]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invitationNotifications: List<OneShotNotification.Append.Meta>,
simpleNotifications: List<OneShotNotification.Append.Meta>,
useCompleteNotificationFormat: Boolean): Notification {
val summaryInboxStyle = NotificationCompat.InboxStyle().also { style ->
Copy link
Contributor Author

@ouchadam ouchadam Oct 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this the all the summary group logic collected into a single function/class https://github.com/vector-im/element-android/blob/b71e2de9acf611d4a50703aed458d1e022d2b105/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt#L465

rather than spread across all other types of notification creation

eventList: MutableList<NotifiableEvent>) {
Timber.v("Render notification events - count: ${eventList.size}")
val notificationEvents = notifiableEventProcessor.modifyAndProcess(eventList, currentRoomId)
if (lastKnownEventList == notificationEvents.hashCode()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the notifiable events becoming immutable models #4182 we can check for changes and skip redundant render passes where the notifications haven't changed or aren't relevant, eg a room marking a message as read

private fun processEvents(notificationEvents: ProcessedNotificationEvents, myUserId: String, myUserDisplayName: String, myUserAvatarUrl: String?, useCompleteNotificationFormat: Boolean) {
val (roomEvents, simpleEvents, invitationEvents) = notificationEvents
with(notificationFactory) {
val roomNotifications = roomEvents.toNotifications(myUserDisplayName, myUserAvatarUrl)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processEvents is where the all the notifications are created and then afterwards all notifications are rendered

@ouchadam ouchadam force-pushed the feature/adm/notifications-creation-display-split branch from 91f35f2 to b715e9f Compare October 7, 2021 15:46
@github-actions
Copy link

github-actions bot commented Oct 7, 2021

Unit Test Results

  54 files  +12    54 suites  +12   50s ⏱️ +6s
106 tests +23  106 ✔️ +23  0 💤 ±0  0 ±0 
312 runs  +92  312 ✔️ +92  0 💤 ±0  0 ±0 

Results for commit 1c35109. ± Comparison against base commit d5e73cc.

♻️ This comment has been updated with latest results.

val invitationEvents: MutableMap<String, InviteNotifiableEvent?> = LinkedHashMap()

val eventIterator = eventList.listIterator()
while (eventIterator.hasNext()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can further improve this by only calculating the diff between what we've rendered and the new eventList state

@ouchadam ouchadam force-pushed the feature/adm/immutable-notifiable-models branch from 55681b1 to d5e73cc Compare October 11, 2021 16:57
…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
…l notifications

- adds some comments to explain the positioning
@ouchadam ouchadam force-pushed the feature/adm/notifications-creation-display-split branch from b715e9f to 1c35109 Compare October 11, 2021 16:59
@ouchadam ouchadam mentioned this pull request Oct 12, 2021
@ouchadam
Copy link
Contributor Author

closing to avoid ordering confusion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant