Flutter

Official pushtifly_flutter SDK on top of firebase_messaging, or integration via REST.

The official SDK pushtifly_flutter builds on firebase_messaging and adds what that plugin doesn't do:

  • device registration, with an offline queue;
  • the Android channel;
  • notifications while the app is open on Android, with image and buttons;
  • reported opens and clicks, including the tapped button on iOS;
  • users, tags and topics.

With firebase_messaging the token is FCM on both platforms. You need:

Installation

dependencies:
  firebase_core: ^5.1.0
  pushtifly_flutter:
    path: ../pushtifly_flutter      # until the package is on pub.dev
flutterfire configure

pushtifly_flutter already includes firebase_messaging, flutter_local_notifications, http and shared_preferences.

  • Android: flutter_local_notifications requires core library desugaring in android/app/build.gradle.kts:

    android {
        compileOptions { isCoreLibraryDesugaringEnabled = true }
    }
    dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") }
  • iOS: enable Push Notifications and Background Modes > Remote notifications in Xcode.

Usage

import 'package:firebase_core/firebase_core.dart';
import 'package:pushtifly_flutter/pushtifly_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await Pushtifly.instance.init(const PushtiflyOptions(
    appKey: 'pk_live_YOUR_PUBLIC_KEY',           // PUBLIC key only
    androidChannel: PushtiflyAndroidChannel(id: 'general', name: 'General notices'),
  ));
  Pushtifly.instance.onNotificationOpened((n) {
    // n.deepLink, n.actionId (tapped button), n.data (your data)
    if (n.deepLink != null) router.go(Uri.parse(n.deepLink!).path);
  });
  runApp(const MyApp());
}

// Ask for permission once the user understands what it's for (not on app launch)
final state = await Pushtifly.instance.requestPermission();   // granted, denied, ...

Pushtifly.instance.identify('customer-123');    // on sign-in
Pushtifly.instance.setTags({'plan': 'gold', 'city': 'NYC'});
Pushtifly.instance.subscribe('offers');
Pushtifly.instance.logout();                    // on sign-out

Register onNotificationOpened at startup: if the app was opened from a notification, the SDK keeps that open and delivers it as soon as you subscribe.

Calls (identify, setTags, subscribe) don't wait for the network: they're stored in a persistent queue. They're sent when there's a token and a connection, and retried when the user comes back to the app.

PushtiflyOptions options

Option Default Description
appKey — Public key pk_live_… or pk_test_…. The SDK rejects secret keys.
appId — Only if the key isn't restricted to one app.
autoRegister false Ask for permission at launch.
foregroundDisplay true Show notifications while the app is open.
androidChannel — Channel the SDK creates; it must match the app's default channel in Pushtifly.
iosNotificationServiceExtension false The app includes the extension (see below); enables images and buttons on iOS.
apiUrl https://api.pushtifly.com Only for testing.
debug false Logs in the console.

Test device

In Devices, the panel shows a 6-digit pairing code; in the app:

await Pushtifly.instance.pairTestDevice('483920');

Images and buttons on iOS

  1. In Xcode (ios/Runner.xcworkspace): File > New > Target > Notification Service Extension.
  2. Replace the generated NotificationService.swift with the package's: ios/NotificationService/NotificationService.swift.
  3. Start the SDK with iosNotificationServiceExtension: true.

On Android, the image is shown with no extra steps. Buttons appear when the notification arrives while the app is open (the SDK shows it with flutter_local_notifications).

A send with a carousel (2 to 5 images) looks like this with the SDK; without these steps, the phone shows only the first image.

Android

The carousel arrives as a data message and the SDK draws it, even when the app is closed. Register firebase_messaging's background handler before runApp and turn on androidCarousel:

@pragma('vm:entry-point')
Future<void> onBackgroundMessage(RemoteMessage message) =>
    PushtiflySdk.handleBackgroundMessage(message.data, notification: message.notification != null);

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
  await Pushtifly.instance.init(const PushtiflyOptions(appKey: 'pk_live_…', androidCarousel: true));
  runApp(const MyApp());
}

iOS

The carousel needs, besides the Service Extension, a Notification Content Extension:

  1. In Xcode: File > New > Target > Notification Content Extension.
  2. Replace its NotificationViewController.swift and Info.plist with the package's: ios/NotificationContent/. Delete the generated MainInterface.storyboard.
  3. Also use the updated Service Extension template (ios/NotificationService/): it downloads all the images.
  4. Start the SDK with iosNotificationServiceExtension: true and iosNotificationContentExtension: true.

On long press the carousel appears; the View button opens the app with the visible image's link.

Firebase Installation ID

If you turn on Firebase's Installation ID mode in the app, the SDK detects it and registers the FID instead of the registration token, with no changes to your code. See Firebase Installation ID.

What the SDK reports

Event When
received The notification arrives while the app is open.
opened The user taps the notification.
clicked The user taps a button (includes actionId).
permission_granted / permission_denied The permission changes.

Without the SDK (REST)

Register the FCM token yourself with POST /v1/devices:

final token = await FirebaseMessaging.instance.getToken();
await http.post(
  Uri.parse('https://api.pushtifly.com/v1/devices'),
  headers: {'Authorization': 'Bearer pk_live_YOUR_PUBLIC_KEY', 'Content-Type': 'application/json'},
  body: jsonEncode({'token': token, 'platform': Platform.isIOS ? 'ios' : 'android', 'provider': 'fcm', 'framework': 'flutter'}),
);

The send's id arrives in data['_phid'] and the link in data['deepLink']. Report it to /v1/events on open.