- Add comprehensive unit tests for debugSendNativeEvent, saveDeviceToken, setAuthToken, setSigningKey, clearAuthToken methods - Add unit tests for intentPayload helper method with mocked Intent/Bundle - Add test for unknown method returning notImplemented - Replace placeholder comment with actual test implementations - Use Mockito to mock android.jar stubs (Intent, Bundle) for gradle unit tests |
||
|---|---|---|
| .. | ||
| android | ||
| docs | ||
| ios | ||
| lib | ||
| macos | ||
| test | ||
| .gitignore | ||
| .metadata | ||
| analysis_options.yaml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| pubspec.yaml | ||
| README.md | ||
nexo_messaging
Android-first Flutter plugin for the nexo 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 server signing-key storage.
SDK Ownership Boundary
This package is the nexo-owned embedded messaging SDK. Its Android
implementation follows the nexo server/push contract, Android and FCM platform
changes, and Flutter plugin registration behavior. It does not follow or mirror
the Mattermost mobile app source tree; that app remains under apps/mattermost/
as an upstream clone.
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/flutter-test/ # Flutter test host
The workspace apps/flutter-test/ app validates plugin integration without
turning this repository into a product-app workspace. The original Mattermost app
belongs under apps/mattermost/, not in the test host.
How It Works
graph TD
A[nexo 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
NexoMessagingPlugin.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 nexo 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:
nexo_messaging:
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:nexo_messaging/nexo_messaging.dart';
Initialize the plugin:
await NexoMessagingPlugin.instance.initialize();
Listen to raw notification events:
NexoMessagingPlugin.instance.onNotification.listen((data) {
// data includes the native payload plus a push type.
});
Listen to notification-open events:
NexoMessagingPlugin.instance.onNotificationOpened.listen((event) {
final serverUrl = event.serverUrl;
final channelId = event.channelId;
final rootId = event.rootId;
});
Register navigation callbacks:
NexoMessagingPlugin.instance.onNavigateToChannel = (
String serverUrl,
String channelId,
) {
// Navigate to the channel in the host app.
};
NexoMessagingPlugin.instance.onNavigateToThread = (
String serverUrl,
String rootId,
) {
// Navigate to the CRT thread in the host app.
};
Handle device-token registration:
NexoMessagingPlugin.instance.onDeviceTokenReady = (String deviceToken) {
// Example format: android_rn-v2:YOUR_FCM_TOKEN
// Send this token to the nexo server.
};
Store credentials and signing keys:
await NexoMessagingPlugin.instance.setAuthToken(
'https://nexo.example.com',
'USER_AUTH_TOKEN',
identifier: 'user_session_id',
);
await NexoMessagingPlugin.instance.setSigningKey(
'https://nexo.example.com',
'SERVER_PUBLIC_SIGNING_KEY',
);
await NexoMessagingPlugin.instance.clearAuthToken(
'https://nexo.example.com',
);
final token = await NexoMessagingPlugin.instance.getDeviceToken();
Event Types
The Dart API currently recognizes these native event types:
| Type | Meaning |
|---|---|
message |
A nexo 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
NexoFirebaseMessagingService 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/flutter-test/android/local.properties.
Development Commands
Run Dart analysis and unit tests from the repository root:
flutter analyze
flutter test
Run flutter-test checks from the app path:
cd apps/flutter-test
flutter analyze
flutter test
flutter test integration_test
Run Android native unit tests:
cd apps/flutter-test/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/flutter-test/test/covers the thin Flutter test app.apps/flutter-test/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 nexo server ACK flows because they depend on external infrastructure.
Manual Smoke Checklist
Use this checklist before promoting a plugin change to a consuming app. These checks require external Firebase and nexo server infrastructure, so they are not expected to pass in a headless repository test run.
Prerequisites:
- Firebase is configured for the host app with
android/app/google-services.json. - The host app has called
NexoMessagingPlugin.instance.initialize(). - The test user has a server URL, auth token, and signing key saved through the plugin API.
- The nexo server can send a push payload with
ack_id,server_urlor resolvableserver_id,channel_id, and a valid signature.
Evidence to capture:
-
logcatlines fromNexoFirebaseMessagingServicefor receipt, ACK, and signature handling. -
A screenshot or device recording of the displayed notification.
-
Server-side ACK/reply request logs showing response status.
-
Host app UI evidence for
onNotificationOpened,onNavigateToChannel, andonNavigateToThreadrouting. -
FCM payload wakes
NexoFirebaseMessagingService. -
Valid nexo 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 nexo 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/flutter-test/app should stay focused on package integration behavior.