The pushix_flutter package registers Android phones and iPhones with Pushix, shows notifications, tells your app when one is tapped, and shows in-app messages.
Requirements
- Flutter 3.32 or newer (Dart 3.8+)
firebase_core5 andfirebase_messaging17 (earlier versions hide foreground notifications on iOS). If your app uses other FlutterFire plugins, move them to their matching versions too.- Android
minSdk24 andcompileSdk36 - iOS 15.0 or newer, Xcode 16, and a paid Apple Developer account (push needs an APNs key)
- One Firebase project with your Android app and your iOS app in it.
1. Add Firebase to the app
The quickest way is the FlutterFire CLI:
dart pub global activate flutterfire_cli
flutterfire configureThis adds google-services.json (Android) and GoogleService-Info.plist (iOS). Use the Firebase project whose service account you upload to Pushix. A different project makes every send fail with SENDER_ID_MISMATCH.
2. Add the package
Copy the sdks/flutter folder from the Pushix download next to your app as pushix_flutter (or put it in a private Git repository) and add it to pubspec.yaml:
dependencies:
firebase_core: ">=5.0.0 <6.0.0"
pushix_flutter:
path: ../pushix_flutter
# or from Git:
# pushix_flutter:
# git:
# url: https://github.com/you/pushix_flutter.gitIf the app already uses another push SDK, remove it: two push SDKs in one app fight over Firebase messaging.
3. Android setup
Desugaring — required by flutter_local_notifications. In android/app/build.gradle.kts:
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}With the older build.gradle (Groovy): coreLibraryDesugaringEnabled true and coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'.
AndroidManifest.xml — inside <application>:
<!-- Lets notification buttons work -->
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />
<!-- Default channel and icon for notifications Android shows while the app is closed -->
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="pushix_default" />
<meta-data android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_notification" />Notification icon — Android draws the small icon as a white silhouette, so a full-colour launcher icon shows up as a white square. Create a white-on-transparent icon (Android Studio → New → Image Asset → Notification Icons), name it ic_stat_notification, and pass defaultSmallIcon: '@drawable/ic_stat_notification' to initialize. You can also set it once for all sends in the dashboard (App settings → Android defaults).
Keep icons and sounds in release builds — icons and sounds named only in messages (small_icon, android_sound) look unused to the build and get removed by resource shrinking. Create android/app/src/main/res/raw/keep.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/ic_stat_*,@raw/*" />If a message names an icon or sound that doesn't exist, the SDK falls back to the default icon and sound instead of dropping the notification.
POST_NOTIFICATIONS (Android 13+) is already declared by the Firebase plugin.
4. iOS setup
iPhone notifications travel from Pushix through Firebase to Apple (APNs). Four one-time steps:
APNs key in Firebase. In the Apple Developer site create a key with Apple Push Notifications service (APNs) and download the .p8 file. In Firebase → Project settings → Cloud Messaging → Apple app configuration, upload it with its Key ID and your Team ID. One key works for all your apps.
Capabilities. Open ios/Runner.xcworkspace in Xcode, select the Runner target → Signing & Capabilities, and add Push Notifications and Background Modes with Remote notifications ticked.
Deployment target. In ios/Podfile set platform :ios, '15.0', and the same under Runner → General → Minimum Deployments.
AppDelegate. In ios/Runner/AppDelegate.swift, make the app the notification delegate so notifications the SDK shows while the app is open appear:
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}Pictures and buttons on iPhone (optional)
iOS only shows a picture (big_picture or ios_attachments) and buttons when the app has a Notification Service Extension. Pushix has one ready:
- Open
ios/Runner.xcworkspacein Xcode: File → New → Target → Notification Service Extension, name itNotificationServiceand click Finish (choose "Cancel" if Xcode offers to activate the scheme). - Add Pushix's extension library to it in
ios/Podfile, then runcd ios && pod install:
target 'NotificationService' do
use_frameworks!
pod 'Pushix/Extension', :path => '../../pushix/sdks/ios' # the sdks/ios folder from your Pushix download
end- Replace the generated
NotificationService.swiftwith:
import Pushix
class NotificationService: PushixNotificationServiceExtension {}The extension downloads the picture and adds the message's buttons (it registers a category for them); it needs no Firebase code. Deployment target 13.0 or later.
Your own button categories (optional)
With the extension above, buttons need nothing else. If your app registers its own categories, you can still send one by name. Register it once in AppDelegate:
let claim = UNNotificationAction(identifier: "claim", title: "Claim", options: [.foreground])
let later = UNNotificationAction(identifier: "later", title: "Later", options: [])
UNUserNotificationCenter.current().setNotificationCategories([
UNNotificationCategory(identifier: "OFFER_ACTIONS", actions: [claim, later], intentIdentifiers: [])
])and send "ios_category": "OFFER_ACTIONS" (composer → iOS options → Category). The tapped button's identifier arrives as event.result.actionId.
Badges
Send ios_badgeType Increase or SetTo with ios_badgeCount. Pushix keeps each iPhone's count and resets it to zero on the server whenever the app opens. To clear the number on the icon itself when the app opens, use a badge plugin such as app_badge_plus.
5. Initialize
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:pushix_flutter/pushix_flutter.dart';
final navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await Pushix.initialize(
'YOUR_APP_ID',
serverUrl: 'https://pushix.in',
defaultSmallIcon: '@drawable/ic_stat_notification', // Android
navigatorKey: navigatorKey, // optional, for in-app messages
);
Pushix.Notifications.addClickListener((event) {
final data = event.notification.additionalData ?? {};
// route to a screen, e.g. data['screen'] == 'orders'
});
runApp(MaterialApp(navigatorKey: navigatorKey, home: const HomePage()));
}Then ask for permission at a sensible moment (iPhone and Android 13+ show the system dialog):
await Pushix.Notifications.requestPermission(true);When the user logs in to your app, link the device to your user id so your backend can target them:
Pushix.login(user.id.toString()); // on login
Pushix.logout(); // on logout (also clears tags)That's it. Open the app once and the device appears in the dashboard under Subscribers, marked Android or iOS.
API overview
| Call | What it does |
|---|---|
Pushix.initialize(appId, serverUrl: ...) | Starts the SDK. Options: defaultSmallIcon, navigatorKey, autoOpenWebUrls, inAppMessages, registerBackgroundHandler |
Pushix.Debug.setLogLevel(PushixLogLevel.verbose) | How much the SDK logs (none … verbose) |
Pushix.login(id) / Pushix.logout() | Links the device to your user id / unlinks it (also clears tags) |
Pushix.consentRequired(true) / Pushix.consentGiven(true) | Registers only after the user agrees |
Pushix.Notifications.requestPermission(true) / .permission / addPermissionObserver | Notification permission |
Pushix.Notifications.addClickListener / addForegroundWillDisplayListener / addSilentDataListener (and remove…) | Taps, notifications arriving while the app is open, data-only messages |
Pushix.Notifications.clearAll() | Removes the app's notifications from the shade |
Pushix.User.addTag(k, v) (or addTagWithKey) / addTags(map) / removeTag / removeTags / getTags() | Tags |
Pushix.User.setLanguage('hi') / getExternalId() / getPlayerId() | Message language, your user id, this device's player id |
Pushix.User.pushSubscription.id / .token / .optedIn / optIn() / optOut() / addObserver | Player id, FCM token and opt-in state of this device |
Pushix.InAppMessages.addTrigger(k, v) / addTriggers / removeTrigger / removeTriggers / clearTriggers | In-app message triggers |
Pushix.InAppMessages.paused(true) / arePaused() | Holds in-app messages back |
Pushix.InAppMessages.addClickListener / addWillDisplayListener / addDidDisplayListener / addWillDismissListener / addDidDismissListener | In-app message events |
Pushix.handleBackgroundMessage(message) | For apps with their own Firebase background handler |
The main types are PushixNotification, PushixNotificationClickEvent, PushixNotificationWillDisplayEvent and PushixInAppMessageClickEvent.
Handling taps
Pushix.Notifications.addClickListener((event) {
final n = event.notification;
print(n.notificationId); // id from the REST API
print(n.additionalData); // your "data" object
print(n.launchUrl); // your "url"
print(event.result.actionId); // button id, or null for the notification itself
});- Taps that open the app from closed are delivered as soon as you add the first click listener, so add it early in
main(). http://andhttps://URLs open in the browser automatically. Turn that off withautoOpenWebUrls: falseininitialize.- Deep links (
myshop://orders) are not opened automatically; readevent.result.urland navigate yourself. - Each tap is reported to the server and shows up as “Clicked” in the dashboard.
While the app is open
Notifications are shown even when the app is in the foreground, on Android and iPhone. To hide or delay one:
Pushix.Notifications.addForegroundWillDisplayListener((event) {
if (currentScreen == 'orders') {
event.preventDefault(); // don't show it
// event.notification.display(); // or show it later
}
});On iOS the SDK turns off Firebase's own foreground banners so a message isn't shown twice. That also applies to pushes from other senders in your app, which aren't shown while it is open.
In-app messages
Create them in the dashboard under In-app messages: a pop-up, card, top or bottom banner, or full screen, with your colours, picture and up to three buttons. The SDK downloads the ones meant for this device each time the app opens and shows them by their trigger:
| Trigger | Shows |
|---|---|
| When the app opens | On each new session (launch, or back after 30+ seconds) |
| From the Nth session | When the app opens and the device has had at least N sessions |
| After some time in the app | N seconds into the session |
| When my code sets a trigger | When your code calls addTrigger with a matching key and value |
Pushix.InAppMessages.addTrigger('cart_value', 1200);
Pushix.InAppMessages.paused(true); // e.g. during a game round
Pushix.InAppMessages.paused(false);
Pushix.InAppMessages.addClickListener((event) {
// event.result.actionId is the button id; event.result.url its link
if (event.result.action == 'deep_link' && event.result.url == 'myshop://account') {
navigatorKey.currentState?.pushNamed('/kyc');
}
});Frequency rules (show at most N times, wait between showings) are kept on the device. "Open a link" buttons open the browser; "Deep link" buttons are handed to your click listener (or opened with the system if you have none). Seen, clicked and closed counts appear in the dashboard and in Analytics.
In-app messages need a MaterialApp (or WidgetsApp) with a Navigator. The SDK finds it automatically; pass navigatorKey to initialize if your app has more than one Navigator, and inAppMessages: false to turn the feature off.
Tags
Tags are key–value strings used for segments, filters and {{ personalisation }}.
Pushix.User.addTags({'level': 7, 'plan': 'gold', 'last_bonus': DateTime.now().millisecondsSinceEpoch ~/ 1000});
Pushix.User.removeTag('plan');
final tags = await Pushix.User.getTags();Your backend can also set tags by user id without the app: PUT /api/v1/apps/{app_id}/users/{external_user_id}.
Changes made while offline are saved on the phone and sent the next time the app opens.
Data-only messages
Messages sent with content_available: true and no contents don't show anything. While the app is open you get them here:
Pushix.Notifications.addSilentDataListener((n) {
if (n.additionalData?['refresh'] == 'balance') reloadBalance();
});When the app is in the background they arrive in Firebase's background handler (see below). iOS limits how often background messages wake an app.
If your app already has a background handler
initialize registers a Firebase background handler that draws Android notifications with buttons or a large icon while the app is closed. Firebase allows only one. If you already call FirebaseMessaging.onBackgroundMessage, pass registerBackgroundHandler: false and call Pushix from your handler:
@pragma('vm:entry-point')
Future<void> myBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
await Pushix.handleBackgroundMessage(message);
// ...your own code
}How notifications are drawn
- Plain messages (title, text, picture) are sent as normal notifications. Android and iOS show them even if the app was swiped away.
- On Android, messages with buttons, a large icon or
android_groupare sent as data messages and drawn by the SDK. These need the app process to start, which some phone makers (Xiaomi, Oppo, Vivo, Realme) block when battery saving is aggressive. Prefer plain messages for anything important.
Testing
- Run the app on your phone and allow notifications. On iPhone use a real device; the simulator only receives pushes on recent Xcode with Apple silicon.
- In the dashboard, open Subscribers, click your device and tick Test device.
- New message → Send to test devices.
Troubleshooting
| Problem | Fix |
|---|---|
| Device never appears in Subscribers | Check serverUrl (must be reachable from the phone over HTTPS) and the App ID. Turn on Pushix.Debug.setLogLevel(PushixLogLevel.verbose) and read the logs. |
| Every send fails with SENDER_ID_MISMATCH | The service account in the dashboard is from a different Firebase project than google-services.json / GoogleService-Info.plist. |
| iPhone never registers ("No APNs token yet" in the log) | Add the Push Notifications capability, check the bundle id matches the iOS app in Firebase, and test on a real device. |
| iPhone registers but nothing arrives | Upload the APNs key in Firebase → Cloud Messaging. Firebase reports this as an authentication error in the message's error. |
| iPhone: nothing shows while the app is open | Add the UNUserNotificationCenter.current().delegate line to AppDelegate, and use firebase_messaging 17 or newer. |
| iPhone: no picture or no buttons | Add the Pushix Notification Service Extension (see iOS setup). |
| Notification shows as a white square (Android) | Use a white-on-transparent small icon (see Android setup). |
| No pop-up, only an icon in the status bar (Android) | The channel isn't high importance. Use the default pushix_default channel, or create your own with Importance.high. If the user lowered it in system settings, only they can raise it again. |
| Works when open, nothing when closed (Xiaomi etc.) | Ask users to allow Autostart / disable battery optimisation for the app, and send plain messages (no buttons or large icon). |
| Buttons don't respond (Android) | Add the ActionBroadcastReceiver to the manifest. |
| Custom sound doesn't play (Android) | On Android 8+ the channel decides the sound. Create a separate channel with that sound in your app and send its id as android_channel_id. |
| In-app message never shows | Check its status is Live, the platform is ticked, and its frequency hasn't been used up on this device. Messages are fetched when a session starts. |
| Build error about desugaring | Add the desugaring lines from Android setup. |
Send your first notification
Create an app in Pushix to get your App ID and REST API key, then follow this guide. The free plan needs no card.