Set Up Push Notifications (Flutter)
This guide shows how to set up push notifications with the OpenLynk Flutter SDK. Push notifications can include deep link destinations, so tapping a notification navigates the user to a specific screen in your app.
Push notification support is currently available in the Flutter SDK only. The iOS and Android native SDKs do not have built-in push support yet. If you need push notifications with a native SDK, use Firebase Cloud Messaging directly and pass the deep link data to the SDK's handleIncomingURL or handleIncomingUri method.
Prerequisites
Before you begin, make sure you have:
- A Firebase project with Cloud Messaging enabled
- The
firebase_messagingpackage added to your Flutter app - Firebase credentials uploaded to the OpenLynk dashboard (see below)
- The OpenLynk Flutter SDK initialized in your app
Upload Firebase Credentials
- Go to Firebase Console -> your project -> Project Settings -> Service accounts.
- Click Generate new private key and download the JSON file.
- In the OpenLynk dashboard, go to your app -> Settings -> Firebase Credentials.
- Upload the service account JSON file.
- Click Test Connection to verify it works.
Step 1: Register Device Token
After initializing Firebase Messaging, register the device token with OpenLynk so it can send push notifications to this device.
import 'package:firebase_messaging/firebase_messaging.dart';
Future<void> setupPush() async {
final messaging = FirebaseMessaging.instance;
// Request permission (required on iOS)
await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
// Get the current token and register it
final token = await messaging.getToken();
if (token != null) {
await sdk.registerPushToken(token, userEmail: currentUser?.email);
}
}
The userEmail parameter is optional but recommended. It lets OpenLynk associate the device with a user for targeted notifications.
Step 2: Handle Token Refresh
FCM tokens can change at any time. Listen for refreshes and re-register with OpenLynk.
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
sdk.registerPushToken(newToken, userEmail: currentUser?.email);
});
Step 3: Handle Foreground Push
When a push notification arrives while the app is in the foreground, Firebase delivers it through onMessage. Pass the data to the SDK if it contains an OpenLynk destination.
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.data.containsKey('destinationPath')) {
// Option A: Navigate immediately
sdk.handlePushPayload(message.data);
// Option B: Show a local notification first, then navigate on tap
// showLocalNotification(message);
}
});
For foreground notifications, consider showing a local notification (using flutter_local_notifications) instead of navigating immediately. This gives the user control over when to act on the notification.
Step 4: Handle Background Tap
When the user taps a notification that arrived while the app was in the background, Firebase delivers the message through onMessageOpenedApp.
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.containsKey('destinationPath')) {
sdk.handlePushPayload(message.data);
}
});
Step 5: Handle Terminated State Launch
When the user taps a notification that launched the app from a terminated state, use getInitialMessage to retrieve it.
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null &&
initialMessage.data.containsKey('destinationPath')) {
sdk.handlePushPayload(initialMessage.data);
}
getInitialMessage() returns null if the app was not launched from a notification. Always check for null before accessing the data.
Push Data Format
OpenLynk push notifications include the following fields in the data payload:
| Key | Type | Description |
|---|---|---|
destinationPath | String | In-app route to navigate to (e.g. /promo/summer) |
metadata | String (JSON) | Additional key-value data as a JSON string |
notificationId | String | OpenLynk notification ID for open tracking |
How handlePushPayload Works
handlePushPayload() does two things:
- Creates a
ParsedDeepLinkfrom the push data and delivers it through theonDeepLinkcallback. This means your navigation logic stays unified -- the sameonDeepLinkhandler processes both deep links and push notifications. - Reports the push as opened to OpenLynk for tracking (if
notificationIdis present in the data).
Complete Working Example
This example combines all five steps into a single, working integration.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:openlynk_sdk/openlynk_sdk.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final OpenlynkSDK _sdk;
final _navKey = GlobalKey<NavigatorState>();
void initState() {
super.initState();
_sdk = OpenlynkSDK(
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
config: OpenlynkSDKConfig(
onDeepLink: (parsed) {
_navKey.currentState?.pushNamed(
parsed.destinationPath,
arguments: parsed.parameters,
);
},
onRestoredLinks: (links) {
final link = links.first;
_navKey.currentState?.pushNamed(
link.destinationPath ?? '/',
arguments: link.parameters,
);
},
),
);
_sdk.init();
_initPush();
}
Future<void> _initPush() async {
final messaging = FirebaseMessaging.instance;
// Step 1: Request permission and register token
await messaging.requestPermission();
final token = await messaging.getToken();
if (token != null) {
await _sdk.registerPushToken(token);
}
// Step 2: Handle token refresh
messaging.onTokenRefresh.listen((t) => _sdk.registerPushToken(t));
// Step 3: Handle foreground push
FirebaseMessaging.onMessage.listen((msg) {
if (msg.data.containsKey('destinationPath')) {
_sdk.handlePushPayload(msg.data);
}
});
// Step 4: Handle background tap
FirebaseMessaging.onMessageOpenedApp.listen((msg) {
if (msg.data.containsKey('destinationPath')) {
_sdk.handlePushPayload(msg.data);
}
});
// Step 5: Handle terminated state launch
final initial = await messaging.getInitialMessage();
if (initial?.data.containsKey('destinationPath') ?? false) {
_sdk.handlePushPayload(initial!.data);
}
}
void dispose() {
_sdk.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navKey,
home: const HomePage(),
routes: {
'/promo/summer': (_) => const PromoPage(code: 'SUMMER20'),
'/product': (_) => const ProductListPage(),
},
);
}
}
What's Next?
- Handle deep links to understand how
onDeepLinkprocesses both deep links and push notifications. - Implement deferred deep linking to preserve link context across the app install flow.
- Learn more about how deep linking works under the hood.