feature/notification/docs/notification-architecture.md
This system is responsible for creating and dispatching all user-facing notifications, including system tray notifications and in-app messages.
At its core, this system uses the Command Design Pattern. The primary goal of this architecture is to decouple the request for a notification from the underlying platform-specific code that displays it. This makes the system more flexible, testable, and easier to extend.
The notification system is organized in the following modules:
The architecture is divided into four main logical groups: Client, Invoker, Command, and Receiver as shown in the diagram:
---
config:
look: neo
layout: elk
---
classDiagram
class Notification
%% The Client that initiates the request
namespace Client {
class SomeAppViewModel {
- sender: NotificationSender
+ onSendNotificationClicked()
}
}
%% The Invoker and its implementation
namespace Invoker {
class NotificationSender {
<<interface>>
+ send(notification: Notification) Flow~Outcome~
}
class DefaultNotificationSender {
- commandFactory: NotificationCommandFactory
}
%% The Factory for creating commands
class NotificationCommandFactory {
+ create(notification: Notification) List~NotificationCommand~
}
}
%% The Command objects
namespace Command {
class NotificationCommand~Notification~ {
<<abstract>>
# notification: Notification
# notifier: NotificationNotifier~Notification~
+ execute() Outcome
}
class SystemNotificationCommand
class InAppNotificationCommand
class Outcome
class CommandOutcome {
<<interface>>
}
class CommandOutcomeSuccess {
<<data>>
}
class CommandOutcomeFailure {
<<data>>
+ throwable: Throwable
}
}
%% The Receivers that perform the action
namespace Receiver {
class NotificationNotifier~Notification~ {
<<interface>>
+ show(id: NotificationId)
+ dispose()
}
class SystemNotificationNotifier
class InAppNotificationNotifier
}
%% External dependencies used by Receivers
namespace Platform Dependencies {
class NotificationManager
}
class InAppNotificationEventBus
class NotificationRegistry
%% Implementation and Inheritance
DefaultNotificationSender --|> NotificationSender
SystemNotificationCommand --|> NotificationCommand
InAppNotificationCommand --|> NotificationCommand
SystemNotificationNotifier --|> NotificationNotifier
InAppNotificationNotifier --|> NotificationNotifier
CommandOutcomeSuccess --|> CommandOutcome
CommandOutcomeFailure --|> CommandOutcome
%% Core Pattern Relationships
SomeAppViewModel "1" --* "1" NotificationSender: uses
SomeAppViewModel ..> Notification: creates
DefaultNotificationSender --> NotificationCommandFactory: uses
NotificationCommandFactory ..> SystemNotificationCommand: creates
NotificationCommandFactory ..> InAppNotificationCommand: creates
NotificationCommand "1" --* "1" NotificationNotifier: has-a
NotificationCommand ..> Outcome: returns
%% Receiver Dependencies
SystemNotificationNotifier ..> NotificationManager: uses
InAppNotificationNotifier ..> InAppNotificationEventBus: uses
InAppNotificationNotifier ..> NotificationRegistry: uses
%% Outcome Composition
Outcome --* "1" CommandOutcome
In the classic Command Pattern, the Client is often responsible for creating the command and setting its receiver. However, in our implementation, the Client's role is simplified.
ViewModel (e.g., ProfileViewModel, SettingsViewModel).Notification data object based on user action or business logic.NotificationSender (the Invoker).notificationSender.send() to initiate the request.Flow<Outcome> to react to the result.The Invoker holds a command and asks it to be executed. It is completely decoupled from the action itself.
NotificationSender (Interface) and DefaultNotificationSender (Concrete Class).DefaultNotificationSender implements the NotificationSender interface.NotificationCommandFactory to get the correct command instances.execute() method on the command list it receives from the factory.The Command object encapsulates all the information required to act.
NotificationCommand (abstract base), with concrete classes like SystemNotificationCommand and
InAppNotificationCommand.Notification (the payload) and a NotificationNotifier (the Receiver).execute() interface that the Invoker can call without knowing the specific details of the
command.The Receiver knows how to perform the work required to carry out the request. It's where the business logic lives.
NotificationNotifier (interface), with concrete classes like SystemNotificationNotifier and
InAppNotificationNotifier.SystemNotificationNotifier uses the Android NotificationManager.InAppNotificationNotifier uses the InAppNotificationEventBus to tell the app that a notification is available
to be displayed. The InAppNotificationScaffold listens for this event and shows the notification.The notification data model is represented by the Notification data model, which acts as the central payload for all
operations. Following, we will breakdown the notification model for better clarity.
The notification model is composed by the following components:
Notification interface, which defines the common properties that all notifications must have. This is a sealed
interface and can't be implemented outside it's package/module.SystemNotification is a subtype of Notification that represents a notification displayed by the system, adding
it's own set of properties which is described in the SystemNotification model section.InAppNotification is a subtype of Notification that represents a notification displayed within the
application, adding it's own set of properties which is described in the SystemNotification model section.AppNotification is an abstract class that provides default properties implementation to easy the app
notification. This is the class you should extend whenever creating a new Notification type.The below diagram describes these components more detailed:
classDiagram
class Notification {
<<interface>>
+ accountUuid: String
+ title: String
+ accessibilityText: String
+ contentText: String?
+ severity: NotificationSeverity
+ createdAt: LocalDateTime
+ actions: Set~NotificationAction~
+ icon: NotificationIcon
}
class AppNotification {
<<abstract>>
+ accessibilityText: String
+ createdAt: LocalDateTime
+ actions: Set~NotificationAction~
}
class SystemNotification {
<<interface>>
}
class InAppNotification {
<<interface>>
}
class NotificationSeverity {
<<enumeration>>
Fatal
Critical
Temporary
Warning
Information
}
class NotificationAction {
<<data>>
+ icon: NotificationIcon?
+ title: String
}
class NotificationIcon {
<<data>>
+ systemNotificationIcon: SystemNotificationIcon?
+ inAppNotificationIcon: ImageVector?
}
Notification --> NotificationSeverity
Notification --> NotificationAction
Notification --> NotificationIcon
NotificationAction --> NotificationIcon
Notification <|-- AppNotification
Notification <|-- SystemNotification
Notification <|-- InAppNotification
The properties of the Notification interface are:
accountUuid: The UUID of the account that owns the notification. This can be used to filter notifications when
deciding to display them.title: The title of the notification.accessibilityText: The text to be used for accessibility purposes.contentText: The main content text of the notification, can be null.severity: The severity level of the notification.createdAt: The date and time when the notification was created.actions: A set of actions that can be performed on the notification.icon: The notification icon.System notifications have their own particularities, which are described in this section. Aside from all the properties
included in the Notification interface, the SystemNotification also includes:
subText: An optional secondary text that appears below the title, can be null.channel: The notification channel which the notification belongs to.systemNotificationStyle: The style of the notification which will explain to Android OS how to display the
notification. Defaults to SystemNotificationStyle.Undefined. For more information about the styles, see
the notification styles' documentationasLockscreenNotification(): A method to convert the SystemNotification to a LockscreenNotification. You should
only override this if you need to display a different notification when displaying in the lockscreen, e.g. hiding the
sender's mail, notification content, etc.classDiagram
class Notification {
<<interface>>
}
class SystemNotification {
<<interface>>
+ subText: String?
+ channel: NotificationChannel
+ systemNotificationStyle: SystemNotificationStyle
+ asLockscreenNotification(): LockscreenNotification?
}
class LockscreenNotification {
<<data>>
+ notification: SystemNotification
+ lockscreenNotificationAppearance: LockscreenNotificationAppearance
}
class LockscreenNotificationAppearance {
<<enumeration>>
None
AppName
Public
MessageCount
}
class NotificationChannel {
<<interface>>
+ id: String
+ name: StringResource
+ description: StringResource
+ importance: NotificationChannelImportance
}
class NotificationChannelImportance {
<<enumeration>>
None
Min
Low
Default
High
}
class SystemNotificationStyle {
<<enumeration>>
BigTextStyle
InboxStyle
Undefined
}
class `SystemNotificationStyle.BigTextStyle` {
<<data>>
+ text: String
}
class `SystemNotificationStyle.InboxStyle` {
<<data>>
+ bigContentTitle: String
+ summary: String
+ lines: List~CharSequence~
}
Notification <|-- SystemNotification
SystemNotification --> SystemNotificationStyle
SystemNotification --> NotificationChannel
SystemNotification <--> LockscreenNotification
LockscreenNotification --> LockscreenNotificationAppearance
SystemNotificationStyle <|-- `SystemNotificationStyle.BigTextStyle`
SystemNotificationStyle <|-- `SystemNotificationStyle.InboxStyle`
NotificationChannel --> NotificationChannelImportance
In-app notifications have their own particularities, which are described in this section. Aside from all the properties
included in the Notification interface, the InAppNotification also includes:
inAppNotificationStyle: The style of the in-app notification. This will explain to the UI how to display the
notification. For more information about the styles, see the notification styles' documentation.classDiagram
class Notification {
<<interface>>
}
class InAppNotification {
<<interface>>
+ inAppNotificationStyle: InAppNotificationStyle
}
class InAppNotificationStyle {
<<enumeration>>
BannerGlobalNotification
BannerInlineNotification
DialogNotification
SnackbarNotification
Undefined
}
class `InAppNotificationStyle.SnackbarNotification` {
<<data>>
+ duration: SnackbarDuration
}
Notification <|-- InAppNotification
InAppNotification --> InAppNotificationStyle
InAppNotificationStyle <|-- `InAppNotificationStyle.SnackbarNotification`
Notification Interface: At the top level is the Notification interface, which contains properties common
to all notification types, such as title, text, severity, and a list of NotificationActions. The
Notification interface should never be directly implemented.AppNotification Class: This is an abstract class that provides default properties implementation to
easy the app notification creation.SystemNotification: Represents a standard Android OS notification. It includes properties for
Android-specific features like the NotificationChannel and NotificationChannelImportance. Additionally it also
let you change how to display the notification via SystemNotificationStyle.InAppNotification: Represents a message shown inside the app's UI. It includes its own
InAppNotificationStyle.SystemNotificationStyle can be a BigTextStyle or InboxStyle, mapping to native Android features.InAppNotificationStyle can be BannerGlobalNotification, BannerInlineNotification, DialogNotification, and
SnackbarNotification.The whole notification model is represented by the following diagram:
classDiagram
class Notification {
<<interface>>
+ accountUuid: String
+ title: String
+ accessibilityText: String
+ contentText: String?
+ severity: NotificationSeverity
+ createdAt: LocalDateTime
+ actions: Set~NotificationAction~
+ icon: NotificationIcon
}
class AppNotification {
<<abstract>>
+ accessibilityText: String
+ createdAt: LocalDateTime
+ actions: Set~NotificationAction~
}
class NotificationSeverity {
<<enumeration>>
Fatal
Critical
Temporary
Warning
Information
}
class NotificationAction {
<<data>>
+ icon: NotificationIcon?
+ title: String
}
class NotificationIcon {
<<data>>
+ systemNotificationIcon: SystemNotificationIcon?
+ inAppNotificationIcon: ImageVector?
}
class SystemNotification {
<<interface>>
+ subText: String?
+ channel: NotificationChannel
+ systemNotificationStyle: SystemNotificationStyle
+ asLockscreenNotification(): LockscreenNotification?
}
class LockscreenNotification {
<<data>>
+ notification: SystemNotification
+ lockscreenNotificationAppearance: LockscreenNotificationAppearance
}
class LockscreenNotificationAppearance {
<<enumeration>>
None
AppName
Public
MessageCount
}
class InAppNotification {
<<interface>>
+ inAppNotificationStyle: InAppNotificationStyle
}
class NotificationChannel {
<<interface>>
+ id: String
+ name: StringResource
+ description: StringResource
+ importance: NotificationChannelImportance
}
class NotificationChannelImportance {
<<enumeration>>
None
Min
Low
Default
High
}
class SystemNotificationStyle {
<<enumeration>>
BigTextStyle
InboxStyle
Undefined
}
class `SystemNotificationStyle.BigTextStyle` {
<<data>>
+ text: String
}
class `SystemNotificationStyle.InboxStyle` {
<<data>>
+ bigContentTitle: String
+ summary: String
+ lines: List~CharSequence~
}
class InAppNotificationStyle {
<<enumeration>>
BannerGlobalNotification
BannerInlineNotification
DialogNotification
SnackbarNotification
Undefined
}
class `InAppNotificationStyle.SnackbarNotification` {
<<data>>
+ duration: SnackbarDuration
}
Notification --> NotificationSeverity
Notification --> NotificationAction
Notification --> NotificationIcon
NotificationAction --> NotificationIcon
Notification <|-- AppNotification
Notification <|-- SystemNotification
Notification <|-- InAppNotification
SystemNotification --> SystemNotificationStyle
SystemNotification --> NotificationChannel
SystemNotification <--> LockscreenNotification
LockscreenNotification --> LockscreenNotificationAppearance
SystemNotificationStyle <|-- `SystemNotificationStyle.BigTextStyle`
SystemNotificationStyle <|-- `SystemNotificationStyle.InboxStyle`
NotificationChannel --> NotificationChannelImportance
InAppNotification --> InAppNotificationStyle
InAppNotificationStyle <|-- `InAppNotificationStyle.SnackbarNotification`