8.1 KiB
messaging_flutter
Android-first Flutter plugin for Nexo's Mattermost-compatible messaging and push-notification runtime.
The plugin owns the push-notification domain that should not be duplicated in each consuming app: native FCM handling, notification display, notification tap events, inline replies, dismiss handling, ACK delivery, device-token storage, auth-token storage, and Mattermost signing-key storage.
Platform Status
| Platform | Status | Notes |
|---|---|---|
| Android | Production target | Native FCM service, Room storage, JJWT verification, OkHttp ACK client, notification builder, inline reply receiver, dismiss receiver |
| iOS | Stub | Plugin scaffold exists, behavior is not implemented yet |
| macOS | Stub | Plugin scaffold exists, behavior is not implemented yet |
Repository Layout
packages/messaging_flutter/
lib/ # Public Dart API and Flutter-facing behavior
android/ # Android native plugin implementation
ios/ # iOS plugin scaffold
macos/ # macOS plugin scaffold
test/ # Dart unit tests for the plugin contract
../../apps/client/ # Workspace client and integration test host
The workspace apps/client/ app is the official independent consumer app for
this package. Use it to validate plugin integration without coupling tests to a
separate product app.
How It Works
graph TD
A[Mattermost Server] -->|Secure JWT Push| B(Android FCM Service)
B -->|Verify signing key| C{Valid?}
C -->|Yes| D[Store payload and build notification]
C -->|No| E[Drop push]
D -->|Send ACK| A
D -->|Display notification| F(Android status bar)
F -->|Tap| G[Flutter opened event]
F -->|Inline reply| H[Reply receiver]
H -->|Send reply| A
G -->|Stream and callbacks| I(Host app navigation)
Host App Responsibilities
The plugin isolates push handling, but the host application still owns app-level integration:
- Configure Firebase for the host app, including
google-services.jsonunderandroid/app/. - Initialize Firebase as required by the host app before push registration.
- Call
MattermostPushPlugin.instance.initialize()during app startup. - Save auth tokens and signing keys after login.
- Clear auth tokens on logout.
- Send the formatted device token to the Mattermost server.
- Connect notification-open callbacks to the host app router.
Installation
For local development or another app in the same workspace, use a path dependency:
dependencies:
mattermost_push_plugin:
path: ../../packages/messaging_flutter
For a private Git dependency, point the consuming app at the repository and pin the branch, tag, or commit according to your release process.
Dart Usage
Import the public API:
import 'package:mattermost_push_plugin/mattermost_push_plugin.dart';
Initialize the plugin:
await MattermostPushPlugin.instance.initialize();
Listen to raw notification events:
MattermostPushPlugin.instance.onNotification.listen((data) {
// data includes the native payload plus a push type.
});
Listen to notification-open events:
MattermostPushPlugin.instance.onNotificationOpened.listen((event) {
final serverUrl = event.serverUrl;
final channelId = event.channelId;
final rootId = event.rootId;
});
Register navigation callbacks:
MattermostPushPlugin.instance.onNavigateToChannel = (
String serverUrl,
String channelId,
) {
// Navigate to the channel in the host app.
};
MattermostPushPlugin.instance.onNavigateToThread = (
String serverUrl,
String rootId,
) {
// Navigate to the CRT thread in the host app.
};
Handle device-token registration:
MattermostPushPlugin.instance.onDeviceTokenReady = (String deviceToken) {
// Example format: android_rn-v2:YOUR_FCM_TOKEN
// Send this token to the Mattermost server.
};
Store credentials and signing keys:
await MattermostPushPlugin.instance.setAuthToken(
'https://mattermost.example.com',
'USER_AUTH_TOKEN',
identifier: 'user_session_id',
);
await MattermostPushPlugin.instance.setSigningKey(
'https://mattermost.example.com',
'SERVER_PUBLIC_SIGNING_KEY',
);
await MattermostPushPlugin.instance.clearAuthToken(
'https://mattermost.example.com',
);
final token = await MattermostPushPlugin.instance.getDeviceToken();
Event Types
The Dart API currently recognizes these native event types:
| Type | Meaning |
|---|---|
message |
A Mattermost message notification |
clear |
Notification clear or dismiss event |
session |
Session-related event |
token_refresh |
Device-token refresh event |
opened |
User opened a notification |
NotificationOpenedEvent parses server_url, channel_id, root_id, and
is_crt_enabled. If CRT is enabled and root_id is present, the plugin calls
onNavigateToThread; otherwise it calls onNavigateToChannel when a channel is
available.
Android Notes
The Android manifest declares the plugin's own
MattermostFirebaseMessagingService and removes the default FlutterFire
messaging components with tools:node="remove". This avoids duplicate FCM
service registration when the host app also depends on firebase_messaging.
Host apps should not redeclare duplicate FCM services unless they intentionally coordinate that behavior with this plugin.
Android Gradle tests require a configured Android SDK through ANDROID_HOME or
apps/client/android/local.properties.
Development Commands
Run Dart analysis and unit tests from the repository root:
flutter analyze
flutter test
Run client-app checks:
cd apps/client
flutter analyze
flutter test
flutter test integration_test
Run Android native unit tests:
cd apps/client/android
./gradlew testDebugUnitTest
If the local machine does not have an Android SDK or Android device/emulator,
use the separate SSH-based Android test environment documented in
docs/android-test-environment.md.
Test Strategy
Use deterministic tests for repository-owned behavior. The example integration test can inject a debug native event through the plugin method channel so the EventChannel and opened-routing path can run without Firebase FCM.
Use the repository tests as the source of truth for plugin behavior:
- Root
test/covers Dart API behavior, event parsing, callback routing, and device-token formatting. android/src/test/should cover native payload parsing, signing-key lookup, local storage, notification behavior, and ACK request behavior.apps/client/test/covers the thin Flutter harness app.apps/client/integration_test/covers real plugin registration, method-channel behavior, event-channel behavior, notification-open routing, and manifest integration on a device or emulator using the test/debug injection path.- Manual smoke tests cover true Firebase FCM delivery and real Mattermost ACK flows because they depend on external infrastructure.
Manual Smoke Checklist
Use this checklist before promoting a plugin change to a consuming app:
- FCM payload wakes
MattermostFirebaseMessagingService. - Valid Mattermost push payload passes signing-key verification.
- Invalid or unsigned payload is dropped.
- System notification displays the expected title, message, avatar, and CRT threading behavior.
- ACK request is sent to the Mattermost server and receives a successful response.
- Tapping a notification launches or resumes the app and emits
onNotificationOpened. - Channel notifications call
onNavigateToChannel. - CRT thread notifications call
onNavigateToThread. - Inline reply action sends the reply through the native receiver.
- Dismiss action clears native notification records as expected.
- Device token is stored with the
android_rn-v2:prefix.
Current Limitations
- Android is the only implemented runtime target.
- iOS and macOS are scaffolded but no-op.
- Full FCM delivery cannot be reliably automated in a headless test runner.
- The
apps/client/app is intentionally small and should stay focused on Nexo client and package integration behavior.