notify_mvp 1.0.3 copy "notify_mvp: ^1.0.3" to clipboard
notify_mvp: ^1.0.3 copied to clipboard

Official Flutter SDK for NotifyMVP — a OneSignal-like push notification platform. Handles FCM token registration, auto-refresh, and device identification with zero boilerplate.

notify_mvp — Flutter SDK #

Official Flutter SDK for NotifyMVP — a self-hosted push notification platform.

What it does #

  • Requests FCM (Firebase Cloud Messaging) permission
  • Registers the device token with your NotifyMVP backend
  • Auto re-registers when the FCM token refreshes
  • Persists a stable deviceId across app restarts using secure storage
  • Retries failed registrations with exponential backoff

Installation #

Add to your app's pubspec.yaml:

dependencies:
  notify_mvp: ^1.0.3
  firebase_core: ^3.0.0
  firebase_messaging: ^15.0.0

Run:

flutter pub get

baseUrl #

baseUrl is your NotifyMVP Cloudflare Worker URL — the origin you get after deploying my-app/ with Wrangler.

  • https://notifymvp.<your-account>.workers.dev
  • or custom domain: https://notify.yourdomain.com

No trailing slash. Not a shared public API.


Setup #

1. Configure Firebase #

Follow the FlutterFire setup guide to:

  1. Create a Firebase project
  2. Add your Android/iOS apps
  3. Download google-services.json (Android) and GoogleService-Info.plist (iOS)
  4. Run flutterfire configure to generate firebase_options.dart

2. Android — android/app/build.gradle (or build.gradle.kts) #

A. Apply Google Services Plugin:

plugins {
    id 'com.google.gms.google-services'
}

B. Enable Core Library Desugaring (Required for High-Priority Notifications):

Why is this required?
NotifyMVP Flutter SDK delivers high-priority Heads-Up system notifications (pop-up banner with sound & vibration) using Java 8+ Android APIs. Enabling core library desugaring allows older Android versions to process these APIs without crashing.

If using Groovy (build.gradle):

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
        coreLibraryDesugaringEnabled true
    }
}

dependencies {
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}

If using Kotlin DSL (build.gradle.kts):

android {
    ...
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}

C. Add Default Notification Channel to android/app/src/main/AndroidManifest.xml:

Inside your <application> tag, add this <meta-data> entry so Android FCM assigns background notifications to the High-Priority Pop-up Channel (enabling top pop-up banners on mobile home screen):

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="notifymvp_heads_up_channel" />

3. iOS — ios/Runner/Info.plist #

Add background modes for push notifications:

<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>remote-notification</string>
</array>

Usage #

Initialize in main.dart #

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:notify_mvp/notify_mvp.dart';

/// Background message handler — must be a top-level function
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  try {
    await Firebase.initializeApp();
    // Rich Push (Big Picture / buttons) when server sends notifymvp_rich=1
    await notifyMvpFirebaseBackgroundHandler(message);
  } catch (_) {}
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. Initialize Firebase & FCM Background Handler
  try {
    await Firebase.initializeApp();
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    // 2. Initialize NotifyMVP SDK
    await NotifyMVP.initialize(
      appId: 'app_your_app_id',         // From dashboard → Projects
      apiKey: 'your_api_key_here',      // From dashboard → Projects
      baseUrl: 'https://your-worker.workers.dev', // your Cloudflare Worker URL
      debugLogging: false,              // Set true in development
    );
  } catch (e) {
    debugPrint('NotifyMVP / Firebase init info: $e');
  }

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NotifyMVP App',
      theme: ThemeData.dark(useMaterial3: true),
      home: const HomeScreen(),
    );
  }
}

Rich Push (image, icon, action buttons) #

Send imageUrl / iconUrl from the NotifyMVP dashboard or REST API. The server sets notifymvp_rich=1 on data payloads so the SDK can render Big Picture reliably on Android.

Background handler (required for rich push when app is not in foreground):

import 'package:notify_mvp/notify_mvp.dart';

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  await notifyMvpFirebaseBackgroundHandler(message);
}

Foreground rich display is handled automatically after NotifyMVP.initialize().

Optional action buttons (Android) — include in FCM data:

"actions": "[{\"id\":\"open\",\"title\":\"Open\"},{\"id\":\"later\",\"title\":\"Later\"}]"

Or flat keys: action1_title, action1_id, action2_title, …

Image URLs must be public HTTPS (direct .jpg / .png links work best).

External User ID (OneSignal-style User Linking) #

Link a user ID (e.g., Firebase Auth UID, User Database ID) to target specific users in your notifications:

// Option A: Pass externalUserId on initialize
await NotifyMVP.initialize(
  appId: 'app_a3f9bc12',
  apiKey: 'your_api_key',
  baseUrl: 'https://your-worker.workers.dev', // your Cloudflare Worker URL
  externalUserId: 'user_12345', // Link user immediately
);

// Option B: Link user after login
await NotifyMVP.login('user_12345'); // or NotifyMVP.setExternalUserId('user_12345')

// Unlink user on logout
await NotifyMVP.logout();

When a user taps a notification (from a foreground banner pop-up, background notification tray, or killed/terminated app state), the launch URL from the dashboard arrives in the payload.

Listening to Notification Taps (All States: Foreground, Background & Terminated)

@override
void initState() {
  super.initState();

  // 1. Check if app was launched by tapping a notification while terminated (Cold Start)
  NotifyMVP.getInitialNotification().then((payload) {
    if (payload?.url != null) {
      debugPrint('Launched from terminated state with URL: ${payload!.url}');
      _handleDeepLink(payload.url!);
    }
  });

  // 2. Listen to notification taps while app is running (Foreground or Background tray tap)
  NotifyMVP.onNotificationOpened().listen((payload) {
    debugPrint('Notification clicked! Title: ${payload.title}, URL: ${payload.url}');
    if (payload.url != null) {
      _handleDeepLink(payload.url!);
    }
  });
}

void _handleDeepLink(String url) {
  // Example: Navigate using GoRouter, Navigator, or GetX
  // context.go(url);
  // Get.toNamed(url);
}

GetX Integration Example

GetX allows context-less navigation anywhere in your app:

import 'package:get/get.dart';
import 'package:notify_mvp/notify_mvp.dart';

class NotificationService extends GetxService {
  @override
  void onInit() {
    super.onInit();
    _listenToNotifications();
  }

  void _listenToNotifications() {
    // 1. Cold start launch (app was killed)
    NotifyMVP.getInitialNotification().then((payload) {
      if (payload?.url != null) _navigateWithGetX(payload!.url!);
    });

    // 2. Foreground / Background notification tap
    NotifyMVP.onNotificationOpened().listen((payload) {
      if (payload.url != null) _navigateWithGetX(payload.url!);
    });
  }

  void _navigateWithGetX(String url) {
    debugPrint('GetX Navigating to: $url');
    if (url.startsWith('/')) {
      Get.toNamed(url);
    } else {
      Get.toNamed('/$url');
    }
  }
}

Payload Data Fields

The NotifyOpenedPayload object contains:

  • payload.title — Notification Title
  • payload.body — Notification Body
  • payload.url — Launch URL / Deep Link string (data['url'] or data['link'])
  • payload.data — Full custom data map sent from dashboard or API

Manual registration #

// Re-register (e.g. after permissions change)
final result = await NotifyMVP.register();

if (result.isSuccess) {
  print('Registered! deviceId: ${result.data?['deviceId']}');
} else {
  print('Failed: ${result.error}');
}

API Reference #

NotifyMVP.initialize(...) #

Parameter Type Required Default Description
appId String ✅ — Your NotifyMVP App ID
apiKey String ✅ — Your NotifyMVP API Key
baseUrl String ✅ — Your Cloudflare Worker URL (https://your-worker.workers.dev or custom domain)
externalUserId String? ❌ null Optional User ID (Firebase UID / DB ID)
debugLogging bool ❌ false Enable console logs
autoRegister bool ❌ true Register on init

Returns NotifyResult — check .isSuccess and .error.

NotifyMVP.login(userId) / setExternalUserId(userId) #

Link this device subscription to a user ID for targeted pushes.

NotifyMVP.logout() #

Unlink user ID from this device subscription.

NotifyMVP.register() #

Manually register or re-register the device. Returns NotifyResult.

NotifyMVP.fcmToken #

String? — the current FCM token. Null until initialized.

NotifyMVP.isInitialized #

bool — whether the SDK has been initialized.


NotifyConfig (advanced) #

Pass a custom config object for fine-grained control:

final config = NotifyConfig(
  appId: 'app_a3f9bc12',
  apiKey: 'your_api_key',
  baseUrl: 'https://your-worker.workers.dev', // your Cloudflare Worker URL
  debugLogging: true,
  requestTimeout: const Duration(seconds: 15),
  maxRetries: 3,
);

await NotifyMVP.initialize(
  appId: config.appId,
  apiKey: config.apiKey,
  baseUrl: config.baseUrl,
  config: config,
);

How it works #

Flutter App                    NotifyMVP Backend            Firebase FCM
    │                                  │                         │
    │── NotifyMVP.initialize() ───────►│                         │
    │                                  │                         │
    │◄── Request FCM permission ───────┤                         │
    │                                  │                         │
    │── getToken() ────────────────────┼────────────────────────►│
    │◄── FCM token ────────────────────┼─────────────────────────│
    │                                  │                         │
    │── POST /api/device/register ────►│                         │
    │   { appId, apiKey, fcmToken,     │                         │
    │     deviceId, platform }         │                         │
    │◄── { success: true } ────────────│                         │
    │                                  │                         │
    │                           Token saved in DB                │
    │                                  │                         │
    │  [later — dashboard send]        │                         │
    │                                  │── FCM multicast ───────►│
    │◄── Push notification ────────────┼─────────────────────────│

Security notes #

  • The apiKey is embedded in your app binary — this is expected and intentional (same model as OneSignal, Firebase, etc.)
  • The API key only allows device registration — it cannot read data, send notifications, or access other projects
  • Sending notifications requires the dashboard (server-side only) — the apiKey alone cannot trigger sends
  • deviceId is stored in encrypted secure storage (flutter_secure_storage)

Platform support #

Platform Status
Android ✅
iOS ✅
Flutter Web ❌ (web push not in scope for MVP)
macOS ⚠️ (untested)
Windows/Linux ❌ (FCM not supported)

Troubleshooting #

"FCM token unavailable — notification permission may be denied" → User denied notification permission. Show a rationale UI and call NotifyMVP.register() again after they grant permission.

"Invalid appId or apiKey" → Double-check your App ID and API Key in the NotifyMVP dashboard → Projects.

"Request failed after 3 attempts" → Check your baseUrl is correct and the server is reachable.

Token not refreshing → The SDK listens to FirebaseMessaging.instance.onTokenRefresh automatically. Make sure NotifyMVP.initialize() is called before runApp().

1
likes
70
points
63
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Official Flutter SDK for NotifyMVP — a OneSignal-like push notification platform. Handles FCM token registration, auto-refresh, and device identification with zero boilerplate.

Homepage

License

unknown (license)

Dependencies

device_info_plus, firebase_core, firebase_messaging, flutter, flutter_local_notifications, flutter_secure_storage, http, package_info_plus, uuid

More

Packages that depend on notify_mvp