Skip to main content

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.

note

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_messaging package 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

  1. Go to Firebase Console -> your project -> Project Settings -> Service accounts.
  2. Click Generate new private key and download the JSON file.
  3. In the OpenLynk dashboard, go to your app -> Settings -> Firebase Credentials.
  4. Upload the service account JSON file.
  5. 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);
}
});
tip

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);
}
caution

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:

KeyTypeDescription
destinationPathStringIn-app route to navigate to (e.g. /promo/summer)
metadataString (JSON)Additional key-value data as a JSON string
notificationIdStringOpenLynk notification ID for open tracking

How handlePushPayload Works

handlePushPayload() does two things:

  1. Creates a ParsedDeepLink from the push data and delivers it through the onDeepLink callback. This means your navigation logic stays unified -- the same onDeepLink handler processes both deep links and push notifications.
  2. Reports the push as opened to OpenLynk for tracking (if notificationId is 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?