# How the Notification System and User Awareness Work in AppFlowy: A Deep Dive

> Explore AppFlowy's notification system and user awareness. Learn how state changes sync from Rust to Flutter and how remote user data is managed within this deep dive.

- Repository: [AppFlowy-IO/AppFlowy](https://github.com/AppFlowy-IO/AppFlowy)
- Tags: deep-dive
- Published: 2026-03-03

---

**AppFlowy uses a centralized notification framework (`flowy-notification`) to broadcast state changes from the Rust core to the Flutter UI, while the `UserAwareness` subsystem manages per-workspace ephemeral state as a Collab document that syncs between local disk and remote servers.**

The notification system and user awareness in AppFlowy form the backbone of real-time collaboration and UI reactivity in the AppFlowy-IO/AppFlowy repository. This architecture decouples the core business logic written in Rust from the Flutter frontend, enabling cross-platform state synchronization while maintaining a lightweight observable pattern for user-specific transient data.

## Notification System Architecture

### NotificationBuilder and Sender Registration

At the heart of the system lies the **`NotificationBuilder`** located in [`frontend/rust-lib/flowy-notification/src/builder.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-notification/src/builder.rs). This struct constructs typed notification payloads containing an identifier, event type, source, and optional protobuf data or error information.

Concrete transport implementations are abstracted behind the **`NotificationSender`** trait defined in [`frontend/rust-lib/flowy-notification/src/lib.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-notification/src/lib.rs) (lines 37-40). Whether the target is a Flutter MethodChannel or Tauri IPC, the core remains agnostic to the delivery mechanism.

### Global Registry and Dispatch Flow

The runtime maintains a **global registry** (`NOTIFICATION_SENDER`) that holds all active `Box<dyn NotificationSender>` instances (lines 14-30 in [`lib.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/lib.rs)). Registration occurs via `register_notification_sender`, typically invoked during application initialization on the UI side.

When dispatching events, the system invokes `send_subject` (lines 82-94), which iterates over registered senders and forwards the constructed `SubscribeObject`:

```rust
// Conceptual flow inside send_subject
pub fn send_subject(subject: SubscribeObject) {
  let senders = NOTIFICATION_SENDER.read();
  for sender in senders.iter() {
    sender.send_subject(&subject);
  }
}

```

### Typed Wrappers for User Events

To avoid boilerplate, the `flowy-user` crate provides convenient wrappers in [`frontend/rust-lib/flowy-user/src/notification.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-user/src/notification.rs). The `send_notification` function constructs a `NotificationBuilder` pre-configured with the **User** observable source, while specialized variants like `send_auth_state_notification` handle authentication-specific events.

Usage sites throughout the codebase emit high-level events such as `DidOpenWorkspace`, `DidUpdateUserProfile`, and `DidLoadUserAwareness`. For example, when `UserManager::open_workspace` completes successfully, it notifies the UI layer:

```rust
// In frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs (lines 44-46)
let pb = UserProfilePB::from(profile);
send_notification(uid, UserNotification::DidOpenWorkspace)
    .payload(pb)
    .send();

```

## User Awareness Subsystem

User awareness represents a **per-workspace, per-user Collab document** storing ephemeral collaborative state including reminders, cursor positions, and presence information. Unlike persistent document data, awareness data is transient and synchronized in real-time.

### Lifecycle: Loading and Initialization

The awareness lifecycle begins in `UserManager::open_workspace`, which triggers `initial_user_awareness` (lines 34-46 in [`manager_user_workspace.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager_user_workspace.rs)). This asynchronous initialization sequence determines whether to hydrate the awareness state from local storage or fetch it from the cloud.

The `initial_user_awareness` method in [`frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs) (lines 29-66) implements the following logic:

1. Checks `is_loading_awareness` to prevent concurrent initialization
2. Determines workspace type (local vs. remote)
3. Routes to local disk or server accordingly

### Local vs. Remote Awareness Loading

When `workspace_type.is_local()` returns true or a local file exists, the system builds the Collab document from disk using `collab_for_user_awareness` (lines 310-340). This method leverages the global `AppFlowyCollabBuilder` to instantiate a `UserAwareness` object with synchronization enabled.

For remote workspaces, `load_awareness_from_server` (lines 91-100) spawns an async task:

```rust
// From manager_user_awareness.rs
async fn load_awareness_from_server(&self, uid: i64, workspace_id: &Uuid, object_id: &str, workspace_type: WorkspaceType) {
  // Fetch document state from cloud service
  match self.cloud_service.get_user_awareness_doc_state(workspace_id).await {
    Ok(doc_state) => {
      let collab = self.collab_for_user_awareness(uid, workspace_id, object_id, doc_state).await?;
      self.user_awareness_by_workspace.insert(*workspace_id, collab);
      // Notify UI that awareness is ready
      send_notification(workspace_id, UserNotification::DidLoadUserAwareness);
    },
    Err(_) => {
      // Create fresh awareness if missing on server
      self.create_default_awareness(workspace_id).await?;
    }
  }
}

```

### Integration with Collab Framework

The `UserAwareness` object itself resides in `user_awareness_by_workspace`, a concurrent map within `UserManager` (defined in [`mod.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/mod.rs)). Direct modifications—such as adding reminders—operate on this Collab instance:

```rust
// In manager_user_awareness.rs (lines 37-50)
pub async fn add_reminder(&self, reminder_pb: ReminderPB) -> FlowyResult<()> {
  let reminder = Reminder::from(reminder_pb);
  let workspace_id = self.workspace_id()?;
  let awareness = self.get_awareness(&workspace_id).await?;
  
  // Write to Collab document
  awareness.write().await.add_reminder(reminder.clone());
  
  // Persist through collab backend
  self.collab_interact.read().await.add_reminder(reminder).await?;
  Ok(())
}

```

While these mutations update the underlying Collab document immediately, the UI observes changes through the Collab framework's native observable subscriptions rather than explicit notifications. However, the initial load completion always triggers `DidLoadUserAwareness` as shown above.

## Practical Code Examples

### Registering a Notification Sender

On the Dart/Flutter side, you register a sender implementation that bridges to the Rust runtime via FFI:

```dart
void registerFlowySender() {
  final sender = FlowyNotificationSender(
    (SubscribeObject subject) {
      // Deserialize protobuf and dispatch to stream
      final notification = Notification.fromProto(subject);
      _notificationController.add(notification);
    },
  );
  // Register with Rust runtime
  rustApi.registerNotificationSender(sender);
}

```

This corresponds to the `register_notification_sender` function on the Rust side that populates the global `NOTIFICATION_SENDER` vector.

### Emitting Workspace Notifications

When workspace settings change, the core emits typed notifications with optional payloads:

```rust
// Emitting a profile update notification
send_notification(uid, UserNotification::DidUpdateUserProfile)
    .payload(UserProfilePB::from(updated_profile))
    .send();

```

The `send()` call internally invokes `send_subject`, which distributes the message to all registered UI listeners.

### Accessing UserAwareness Data

To read or modify awareness state after initialization:

```rust
// Retrieve awareness for current workspace
let awareness = user_manager.get_awareness(&workspace_id).await?;

// Read reminders
let reminders = awareness.read().await.get_all_reminders();

// Update presence (conceptual)
awareness.write().await.update_cursor_position(position);

```

## Summary

- **Centralized dispatch**: The `flowy-notification` crate provides a publisher-subscriber pattern where `NotificationBuilder` constructs payloads and `send_subject` dispatches to registered `NotificationSender` implementations.
- **Global registration**: UI layers register transport implementations (Flutter channels, Tauri IPC) in the global `NOTIFICATION_SENDER` registry during app initialization.
- **UserAwareness lifecycle**: Each workspace maintains a separate `UserAwareness` Collab document initialized via `initial_user_awareness`, supporting both local disk caching and remote server synchronization.
- **Typed events**: The `flowy-user` crate exposes helper functions like `send_notification` to emit specific events (`DidOpenWorkspace`, `DidLoadUserAwareness`) that the Flutter UI observes to update widgets.
- **Collab integration**: Awareness data persists through the Collab framework, enabling real-time synchronization of ephemeral state such as reminders and cursor positions across clients.

## Frequently Asked Questions

### How does AppFlowy notify the UI when a workspace opens?

When `UserManager::open_workspace` completes in [`manager_user_workspace.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager_user_workspace.rs), it calls `send_notification(uid, UserNotification::DidOpenWorkspace).payload(pb).send()`. This constructs a `NotificationBuilder` that invokes `send_subject`, iterating over the global `NOTIFICATION_SENDER` registry to deliver the event to Flutter listeners via FFI.

### What data is stored in the UserAwareness document?

The `UserAwareness` Collab object stores **ephemeral collaborative state** including user reminders, cursor positions, and presence information. Unlike permanent document content, this data is transient and synchronized in real-time across clients accessing the same workspace.

### How does AppFlowy handle offline awareness loading?

The `initial_user_awareness` method in [`manager_user_awareness.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager_user_awareness.rs) checks if the workspace is local or if a cached file exists on disk. If available, it loads the Collab document locally via `collab_for_user_awareness`. Only if no local cache exists does it fall back to `load_awareness_from_server`, ensuring offline functionality.

### Where is the notification sender implementation defined?

The `NotificationSender` trait is defined in [`frontend/rust-lib/flowy-notification/src/lib.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-notification/src/lib.rs) (lines 37-40), while concrete implementations reside in the UI layer (Dart for Flutter). The Rust core stores these as `Box<dyn NotificationSender>` in the global `NOTIFICATION_SENDER` registry, registering them via `register_notification_sender` during application startup.