Platform guides

Flutter recipe

Register a Flutter app's APNs and FCM tokens with Native Notify using firebase_messaging, and show Android pushes.

1. Set up the project

flutter pub add firebase_core firebase_messaging flutter_local_notifications http
flutterfire configure

flutterfire configure generates firebase_options.dart. On iOS, enable the Push Notifications capability in Xcode — Native Notify sends straight to APNs with your .p8 key, so Firebase needs no APNs key.

2. Register the device and show pushes

lib/native_notify_push.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;

const _registerUrl = 'https://app.nativenotify.com/api/universal/device/register';

/// Registers this device's native token: APNs on iOS, FCM on Android.
/// Returns false when the token is not ready yet or the server refused it.
Future<bool> registerUniversalDevice({
  required int appId,
  required String appToken,
  required String deviceId,
  String? subscriberId,
}) async {
  final messaging = FirebaseMessaging.instance;
  // Nothing is shown without permission (tokens are issued either way).
  await messaging.requestPermission();

  // iOS: the raw APNs token (null until APNs has delivered it). Android: the FCM token.
  final String? token = Platform.isIOS ? await messaging.getAPNSToken() : await messaging.getToken();
  if (token == null) return false; // not ready yet — the next launch registers
  return _post(appId: appId, appToken: appToken, deviceId: deviceId, subscriberId: subscriberId, token: token);
}

/// Android rotates FCM tokens: register the new one under the same device.
StreamSubscription<String> watchUniversalToken({
  required int appId,
  required String appToken,
  required String deviceId,
  String? subscriberId,
}) {
  return FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) {
    if (!Platform.isAndroid) return; // iOS registers its APNs token, not an FCM token
    _post(appId: appId, appToken: appToken, deviceId: deviceId, subscriberId: subscriberId, token: fcmToken);
  });
}

Future<bool> _post({
  required int appId,
  required String appToken,
  required String deviceId,
  required String token,
  String? subscriberId,
}) async {
  final res = await http.post(
    Uri.parse(_registerUrl),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'appId': appId,
      'appToken': appToken,
      'deviceId': deviceId, // one stable id per install
      'platform': Platform.isIOS ? 'ios' : 'android',
      if (subscriberId != null) 'subscriberId': subscriberId,
      'tokens': Platform.isIOS ? {'apnsToken': token} : {'fcmToken': token},
    }),
  );
  if (res.statusCode != 201) {
    // 400/403 answer {"error":{"code","message"}}; a wrong appId/appToken answers 401 as plain text.
    debugPrint('Native Notify register failed (${res.statusCode}): ${res.body}');
    return false;
  }
  return true;
}

final _localNotifications = FlutterLocalNotificationsPlugin();

/// Android pushes are FCM *data* messages — show them yourself.
Future<void> showNativeNotifyMessage(RemoteMessage message) async {
  final data = message.data; // every value is a String
  final title = data['title'];
  if (title == null) return;
  await _localNotifications.initialize(
    settings: const InitializationSettings(android: AndroidInitializationSettings('@mipmap/ic_launcher')),
  );
  await _localNotifications.show(
    id: DateTime.now().millisecondsSinceEpoch ~/ 1000,
    title: title,
    body: data['message'],
    notificationDetails: NotificationDetails(
      android: AndroidNotificationDetails(
        data['channelId'] ?? 'default',
        'Notifications',
        importance: Importance.high,
        priority: Priority.high,
      ),
    ),
    payload: data['body'], // your pushData JSON (+ nn_notification_id / nn_source)
  );
}

An iOS FCM token is rejected with token_platform_mismatch, so the code never sends one. initialize and show take named parameters from flutter_local_notifications 20; on 19 or older, pass them positionally.

3. Wire it up in main()

lib/main.dart
import 'dart:io';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/widgets.dart';

import 'native_notify_push.dart';

// Runs in its own isolate when a data message arrives in the background.
('vm:entry-point')
Future<void> _onBackgroundMessage(RemoteMessage message) async {
  await Firebase.initializeApp(); // pass your DefaultFirebaseOptions.currentPlatform here
  await showNativeNotifyMessage(message);
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // pass your DefaultFirebaseOptions.currentPlatform here

  if (Platform.isAndroid) {
    FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage);
    FirebaseMessaging.onMessage.listen(showNativeNotifyMessage);
  } else {
    // iOS shows the APNs alert itself — also while the app is open:
    await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true);
  }

  const appId = 123; // your app id + token (e.g. from --dart-define)
  const appToken = 'yourAppToken';
  const deviceId = 'a-stable-per-install-id'; // e.g. a UUID saved with shared_preferences
  await registerUniversalDevice(appId: appId, appToken: appToken, deviceId: deviceId);
  watchUniversalToken(appId: appId, appToken: appToken, deviceId: deviceId);

  runApp(const SizedBox()); // your app
}

On iOS, firebase_messaging forwards only FCM-originated notifications to onMessage and onMessageOpenedApp, so Native Notify's direct-APNs pushes never reach those handlers. Handle iOS taps in the AppDelegate's UNUserNotificationCenter delegate and pass them over a method channel, as in the Swift recipe.

4. Save credentials

Save the same Firebase project's service-account JSON and your Apple .p8 key under App Settings → Push credentials. See Push credentials.

5. Send a push

Check one device first with a test send. For other audiences, see Send notifications. This call reaches every registered device:

curl -X POST https://app.nativenotify.com/api/universal/notifications/send \
  -H "Content-Type: application/json" \
  -d '{"appId":123,"appToken":"yourAppToken","title":"Hello Flutter","message":"Sent through APNs/FCM.","audience":{"type":"all"}}'

Next steps

  • Device registration — fields, deregister and errors.
  • Groups — let users subscribe from the app with POST /api/universal/groups/subscribe, then send to the group.