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:
- the Firebase credential in Pushtifly;
- in Firebase, the APNs key for iOS (iOS via FCM).
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_notificationsrequires core library desugaring inandroid/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
- In Xcode (
ios/Runner.xcworkspace): File > New > Target > Notification Service Extension. - Replace the generated
NotificationService.swiftwith the package's:ios/NotificationService/NotificationService.swift. - 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).
Image carousel
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:
- In Xcode: File > New > Target > Notification Content Extension.
- Replace its
NotificationViewController.swiftandInfo.plistwith the package's:ios/NotificationContent/. Delete the generatedMainInterface.storyboard. - Also use the updated Service Extension template (
ios/NotificationService/): it downloads all the images. - Start the SDK with
iosNotificationServiceExtension: trueandiosNotificationContentExtension: 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.