bnotify_flutter

Flutter plugin for BNotify push notifications and in-app messaging.

Install: bnotify_flutter: ^0.1.2 on pub.dev

Wraps the native SDKs:

Web is not included. The plugin Git repository is private (not open source). Install from pub.dev only.

Also useful Link
Hosted HTML guide bnotify.github.io/sdk-docs/flutter.html
Console console.bnotify.com
Example app example/ in this package

Integration guide

Audience: Flutter developers adding BNotify to any existing app
Package: bnotify_flutter 0.1.2
Platforms: Android + iOS (web is not supported)
Console: https://console.bnotify.com/
Native Android SDK (pulled in automatically): JitPack bnotifysdk 1.0.5

This guide is host-app setup only: Console credentials, pubspec, Android Activity/manifest/Gradle, iOS plist/capabilities/extension, Dart calls, and how to verify. Campaigns are created and sent from the Console. This file does not describe how the plugin is implemented internally.


Table of contents

  1. What you get after integration
  2. Prerequisites
  3. Integration checklist
  4. Step 1 — Console: create the project and apps
  5. Step 2 — Add the Flutter package
  6. Step 3 — Dart: initialize, permission, and listeners
  7. Step 4 — Android host setup
  8. Step 5 — iOS host setup
  9. Step 6 — In-app campaigns (logEvent)
  10. Step 7 — Notification tap routing
  11. Optional — Custom Android notification UI
  12. Verification and test plan
  13. Troubleshooting
  14. Production checklist
  15. FAQ
  16. Copy-paste reference

1. What you get after integration

Capability Result
Device registration Device appears in the BNotify Console after a successful initialize + permission
Push notifications System notifications on Android and iOS when the Console sends a campaign
Background / killed app Notifications can still arrive after the user leaves or swipes the app away (after the first successful init + config on device)
Notification tap Dart BNotify.onClick with a screen value you can route on
In-app messages Native in-app UI when Console campaigns match events you log
Device token Dart BNotify.onToken for debugging / your own backend if needed

The Flutter SDK does not send campaigns by itself. Create and send messages from the BNotify Console.


2. Prerequisites

Requirement Notes
Flutter 3.24+ / Dart 3.13+ Matches the plugin pubspec.yaml
A real Android / iOS app (not web) bnotify_flutter has no web implementation
BNotify Console account Project + Android app + iOS app
Android minSdk 24 Required
iOS 13.0+ Required
Physical devices recommended Emulators/simulators are weaker for push
Android package name / iOS bundle id Must match the apps you register in the Console

You will download two files from the Console (never commit real keys to a public repo):

  • Android: bnotify-config.json
  • iOS: bnotify-config.plist

3. Integration checklist

Use this as a sequential punch list. Details are in the sections below.

  1. In the Console, create a project and register Android and iOS apps with the exact applicationId / bundle identifier of this Flutter app.
  2. Download bnotify-config.json and bnotify-config.plist.
  3. Add bnotify_flutter: ^0.1.2 in pubspec.yaml from pub.dev.
  4. Declare assets/bnotify-config.json in pubspec.yaml and keep a copy at android/app/bnotify-config.json.
  5. Add JitPack, set minSdk 24, extend BNotifyFlutterActivity, set singleTask, switch themes to AppCompat.
  6. Add ios/Runner/bnotify-config.plist to the Runner target, enable Push Notifications, optionally add a Notification Service Extension.
  7. In Dart: WidgetsFlutterBinding.ensureInitialized(), subscribe to streams, await BNotify.initialize(...), then requestPermission().
  8. Send a test notification from the Console. Confirm token, tray notification, tap routing, and (optional) in-app via logEvent.

4. Step 1 — Console: create the project and apps

  1. Open https://console.bnotify.com/.
  2. Create a project (or pick an existing one).
  3. Register an Android app whose package name equals your Flutter applicationId (see android/app/build.gradle.ktsdefaultConfig.applicationId).
  4. Register an iOS app whose bundle id equals ios/Runner’s bundle identifier.
  5. Download:
    • bnotify-config.json for Android
    • bnotify-config.plist for iOS

If the package name / bundle id in those files does not match the running app, the device will not register correctly.

JSON shape (values come from the Console — do not invent them):

{
  "projectId": "YOUR_PROJECT_ID",
  "packageName": "com.example.my_app",
  "apiKey": "YOUR_API_KEY",
  "appId": "YOUR_APP_ID",
  "fcmAppId": "YOUR_FCM_APP_ID",
  "fcmProjectId": "YOUR_FCM_PROJECT_ID",
  "fcmApiKey": "YOUR_FCM_API_KEY",
  "fcmSenderId": "YOUR_FCM_SENDER_ID"
}

Keep unused Console fields if the download includes them. Do not share these files publicly.


5. Step 2 — Add the Flutter package

Add the published package from pub.dev.

In the host app pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  bnotify_flutter: ^0.1.2

Then:

flutter pub get

Hosted walkthrough: https://bnotify.github.io/sdk-docs/flutter.html

Do not use a public Git dependency. The plugin source repository is private. Apps should install only from pub.dev.

If you are a BNotify maintainer with GitLab access, you may use a path dependency while developing:

dependencies:
  bnotify_flutter:
    path: ../bnotify_flutter

Declare the Android JSON as a Flutter asset (used by BNotify.initialize(configAsset: …)):

flutter:
  assets:
    - assets/bnotify-config.json

Copy the Console file to:

assets/bnotify-config.json
android/app/bnotify-config.json
ios/Runner/bnotify-config.plist

android/app/bnotify-config.json is required so Android can start BNotify when the process is created from a notification, not only after Dart runs. Copy it into Android assets on each build (Gradle snippet in Step 4).


6. Step 3 — Dart: initialize, permission, and listeners

Call this once at startup. Subscribe to streams before initialize so you do not miss a token or a cold-start tap.

import 'package:bnotify_flutter/bnotify_flutter.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  BNotify.onToken.listen((token) {
    debugPrint('BNotify token: $token');
  });

  BNotify.onMessage.listen((message) {
    debugPrint('BNotify message: ${message.title} ${message.body}');
  });

  BNotify.onClick.listen((click) {
    debugPrint('BNotify click screen=${click.screen}');
    // Navigate using click.screen (see Step 7).
  });

  await BNotify.initialize(
    configAsset: 'assets/bnotify-config.json',
  );

  runApp(const MyApp());
}

After the first frame (or from a settings screen), request notification permission:

await BNotify.requestPermission();
API When to call
BNotify.initialize Once per process, after ensureInitialized
BNotify.requestPermission After initialize; required on Android 13+ and iOS to show alerts
BNotify.logEvent('…') When a user action should match an in-app campaign trigger
BNotify.onToken Debug / send the token to your own server if you need it
BNotify.onMessage Optional; system notifications still show when auto-notifications are on
BNotify.onClick Route the user after they tap a notification
BNotify.setAutoNotifications Android only; see optional custom UI

configAsset is read on Android. iOS ignores it and uses ios/Runner/bnotify-config.plist. You still pass configAsset so one initialize call works on both platforms.

Hot reload does not re-run native Kotlin/Swift. After changing MainActivity, manifests, plist, or the plugin itself, do a full restart (flutter run) or uninstall/reinstall.


7. Step 4 — Android host setup

Do every item in this section. Skipping the Activity or theme steps is the usual reason in-app messages never appear.

7.1 JitPack repositories

The plugin depends on the native Android SDK from JitPack. Add JitPack in both places Flutter apps typically declare repositories.

android/settings.gradle.kts (inside pluginManagement { repositories { … } }):

maven { url = uri("https://jitpack.io") }

android/build.gradle.kts (allprojects.repositories or the equivalent in your Gradle setup):

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

7.2 minSdk 24

In android/app/build.gradle.kts:

defaultConfig {
    applicationId = "com.example.my_app" // must match Console
    minSdk = 24
}

Do not leave Flutter’s default minSdk if it is below 24.

7.3 Copy config into Android assets

Keep android/app/bnotify-config.json (Console download). Add this to android/app/build.gradle.kts above the android { } block so the file is packaged as assets/bnotify-config.json inside the APK:

val bnotifyConfigFile = file("bnotify-config.json")
val bnotifyAssetsDir = file("src/main/assets")

tasks.register<Copy>("copyBnotifyConfigAsset") {
    from(bnotifyConfigFile)
    into(bnotifyAssetsDir)
    onlyIf { bnotifyConfigFile.exists() }
}

tasks.matching { it.name == "preBuild" }.configureEach {
    dependsOn("copyBnotifyConfigAsset")
}

Also keep assets/bnotify-config.json at the Flutter project root (same file) for configAsset.

Add android/app/src/main/assets/bnotify-config.json and android/app/bnotify-config.json to .gitignore if they contain real keys. Do not commit production credentials.

7.4 MainActivity must extend BNotifyFlutterActivity

Replace Flutter’s default activity. File: android/app/src/main/kotlin/…/MainActivity.kt

package com.example.my_app

import com.convex.bnotify_flutter.BNotifyFlutterActivity

class MainActivity : BNotifyFlutterActivity()

Do not extend FlutterActivity or FlutterFragmentActivity. In-app campaigns are shown with AppCompat; the host activity must be BNotifyFlutterActivity.

7.5 AndroidManifest: singleTask, no empty taskAffinity

On the launcher activity:

  • android:launchMode="singleTask"
  • Do not set android:taskAffinity=""

Empty taskAffinity plus a notification tap creates two Recents cards.

Example activity block:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTask"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
    <meta-data
        android:name="io.flutter.embedding.android.NormalTheme"
        android:resource="@style/NormalTheme" />
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Keep INTERNET if you do not already have it:

<uses-permission android:name="android.permission.INTERNET" />

POST_NOTIFICATIONS is merged from the plugin. You still must call BNotify.requestPermission() at runtime on Android 13+.

7.6 AppCompat themes

BNotifyFlutterActivity is an AppCompat activity. Flutter’s default Theme.Black.NoTitleBar / Material3 launch themes will crash or look wrong.

android/app/src/main/res/values/styles.xml:

<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">?android:colorBackground</item>
</style>

Night values (values-night/styles.xml): use Theme.AppCompat.DayNight.NoActionBar (or Light/Dark AppCompat) for both LaunchTheme and NormalTheme.

7.7 Optional: native Gradle config plugin

If you prefer generated Kotlin config instead of (or in addition to) configAsset, you can apply the native Gradle plugin the same way a pure Android app does. Most Flutter apps can skip this and rely on configAsset + android/app/bnotify-config.json.


8. Step 5 — iOS host setup

8.1 Add bnotify-config.plist

  1. Copy the Console plist to ios/Runner/bnotify-config.plist.
  2. In Xcode, select the file → File Inspector → Target Membership → enable Runner.
  3. Confirm packageName in the plist equals the Runner bundle identifier.

8.2 Push Notifications capability

In Xcode → Runner target → Signing & Capabilities:

  • Add Push Notifications.
  • Use an Apple Developer team that can issue a push-enabled provisioning profile.
  • For a Notification Service Extension (below), also add App Groups on Runner and the extension, using the same group id as in the plist (appGroupId if present).

If your Flutter toolchain uses Swift Package Manager for plugins:

flutter config --enable-swift-package-manager

Then flutter pub get / open ios/Runner.xcworkspace and let Flutter resolve the plugin’s iOS package (it pulls the native iOS SDK from GitLab automatically).

If you use CocoaPods only, pod install from ios/ after flutter pub get. The plugin’s podspec fetches the native iOS SDK during prepare.

Minimum deployment target: iOS 13.

8.4 AppDelegate

A stock FlutterAppDelegate is enough. You do not need to call native BNotify APIs from AppDelegate for basic push. Keep GeneratedPluginRegistrant as Flutter generated it.

8.5 Notification Service Extension (images / expanded content)

To show images in the notification banner, add a Notification Service Extension target in Xcode (same as a native iOS BNotify app):

  1. File → New → Target → Notification Service Extension.
  2. Add the BNotify iOS SDK to that extension target (Swift Package: https://gitlab.com/hamza.hafeez/bnotifyiossdk.git, product BNotify).
  3. Enable the same App Group on Runner and the extension.
  4. In NotificationService.swift:
import UserNotifications
import BNotify

private let bnotifyAppGroupId = "group.com.example.my_app"

final class NotificationService: UNNotificationServiceExtension {
    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var bestAttemptContent: UNNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = request.content

        BNotifyExtensionSafe.prepareNotificationContent(
            request,
            appGroupId: bnotifyAppGroupId
        ) { [weak self] content in
            guard let self, let handler = self.contentHandler else { return }
            self.bestAttemptContent = content
            handler(content)
            self.contentHandler = nil
        }
    }

    override func serviceExtensionTimeWillExpire() {
        guard let contentHandler, let bestAttemptContent else { return }
        contentHandler(bestAttemptContent)
        self.contentHandler = nil
    }
}

Replace bnotifyAppGroupId with the App Group you created. It must match the main app’s BNotify config.

Without an NSE, text notifications still work; rich images may not.

8.6 Info.plist / usage strings

You do not add a custom Dart permission plugin. iOS uses the system push prompt via BNotify.requestPermission(). If Xcode asks for a purpose string for related APIs, follow Apple’s current requirements for your extension and App Groups.


9. Step 6 — In-app campaigns (logEvent)

  1. In the Console, create an in-app campaign whose trigger name matches a string you will log (for example app_open).
  2. In Dart, after initialize:
await BNotify.logEvent('app_open');

Call it when the matching user action happens (app open, screen view, button, purchase, and so on). The name must match the Console trigger exactly.

Android in-app UI requires Step 4.4 (BNotifyFlutterActivity) and AppCompat themes. If you skip those, logEvent will not show a campaign even when the Console is correct.


10. Step 7 — Notification tap routing

When the user taps a notification, listen to BNotify.onClick. Use click.screen (and other extras) to navigate.

final navigatorKey = GlobalKey<NavigatorState>();

void listenForClicks() {
  BNotify.onClick.listen((click) {
    final screen = click.screen;
    if (screen == null || screen.isEmpty) return;
    navigatorKey.currentState?.pushNamed(screen);
  });
}

Set the screen value on the campaign in the Console so it matches a route name (or map it in a switch).

BNotifyClick also exposes notificationId, type, action, and extras if you need them.

Subscribe in main() before initialize so a tap that opened a killed app still reaches Dart.


11. Optional — Custom Android notification UI

By default Android shows a system notification. To draw your own UI from Dart instead:

await BNotify.setAutoNotifications(false);
BNotify.onMessage.listen((message) {
  // Show your own banner / dialog using message.title / message.body.
});

iOS always uses the system notification UI. setAutoNotifications has no effect there.

Leave auto-notifications on unless you have a custom in-app banner to replace the tray.


12. Verification and test plan

Work through these on a physical device with a Debug or Release build that contains real Console files.

# Test Expected
1 Cold start, initialize succeeds No exception; onToken eventually prints a non-empty token
2 requestPermission System permission dialog (Android 13+ / iOS); user allows
3 Console → device list This install appears after token + internet
4 Console sends a push, app in foreground Notification (iOS banner / Android tray or onMessage)
5 App in background Tray notification appears
6 Force-stop or swipe away, then send Notification still arrives (after first successful init with packaged Android JSON)
7 Tap the notification App opens; onClick fires; screen matches Console
8 Recents / app switcher One task, not two
9 logEvent with a matching in-app campaign In-app UI appears on Android (AppCompat host) and iOS
10 Image payload + iOS NSE Image renders in the notification

If 6 fails on Android, confirm android/app/bnotify-config.json exists and the Gradle copy task ran (android/app/src/main/assets/bnotify-config.json inside the module).


13. Troubleshooting

Symptom What to check
initialize throws missing config Android: configAsset path listed in pubspec.yaml assets, file exists. iOS: plist in Runner target.
No token Permission denied; wrong package/bundle vs Console; no network; still using placeholder YOUR_API_KEY.
No pushes Device not in Console; permission off; battery restrictions; you sent to the wrong app/platform.
Pushes only while the Flutter UI is open Android JSON not packaged as an asset; first initialize never succeeded so nothing was saved.
Two Recents cards Remove android:taskAffinity=""; use singleTask.
In-app never shows (Android) MainActivity still extends FlutterActivity; themes not AppCompat; trigger name ≠ Console; need full rebuild after Activity change.
In-app never shows (iOS) Plist missing from target; campaign platform/app mismatch; logEvent name mismatch.
Crash on launch (Android theme) LaunchTheme / NormalTheme must use Theme.AppCompat.*.
Permission button seems to “click” a notification You are on an old plugin; current requestPermission only requests POST_NOTIFICATIONS. Upgrade to the latest pub.dev version.
iOS images missing No Notification Service Extension / App Group / BNotifyExtensionSafe.prepareNotificationContent.
Gradle cannot find bnotifysdk JitPack missing from settings.gradle.kts and build.gradle.kts.
minSdk error Set minSdk = 24 in the app module.
Changes to Kotlin/Swift not visible Hot reload is not enough. Stop the app and flutter run again.

14. Production checklist

  • Console Android package name = applicationId
  • Console iOS bundle id = Xcode Runner bundle id
  • Real bnotify-config.json / .plist on the device (not placeholders)
  • Those files are not in a public git repo
  • bnotify_flutter: ^0.1.2 from pub.dev
  • minSdk 24, JitPack present
  • MainActivity extends BNotifyFlutterActivity
  • launchMode="singleTask", no empty taskAffinity
  • AppCompat launch/normal themes
  • Android config copied into src/main/assets
  • iOS Push capability (and NSE if you send images)
  • initialize + requestPermission in release builds
  • onClick routing tested from a killed app
  • Release signing / APNs production setup as required by Apple and Play

15. FAQ

Is this on pub.dev?
Yes. Use bnotify_flutter: ^0.1.2 as in Step 2. The Git repository is private; do not depend on it via git:.

Do I need a custom Application class on Android?
No. Put config in android/app/bnotify-config.json and call BNotify.initialize from Dart.

Do I need Firebase / google-services.json in the Flutter app?
Use the FCM fields inside the Console bnotify-config.json. Follow the Console download; do not invent keys.

Can I use this on web?
No. Android and iOS only.

Why BNotifyFlutterActivity?
Native in-app UI requires an AppCompat host. Flutter’s default activity types do not provide that. Extending BNotifyFlutterActivity is a required host change, not an optional extra.

Does configAsset replace the iOS plist?
No. Keep both. Android uses the JSON; iOS uses the plist.

Can I initialize later than main()?
You can, but you will miss cold-start taps and possibly the first token. Prefer main() as shown.


16. Copy-paste reference

pubspec.yaml (host)

dependencies:
  bnotify_flutter: ^0.1.2

flutter:
  assets:
    - assets/bnotify-config.json

lib/main.dart

import 'package:bnotify_flutter/bnotify_flutter.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  BNotify.onClick.listen((click) {
    debugPrint('screen=${click.screen}');
  });
  BNotify.onToken.listen(debugPrint);

  await BNotify.initialize(configAsset: 'assets/bnotify-config.json');
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: FilledButton(
            onPressed: () => BNotify.requestPermission(),
            child: const Text('Enable notifications'),
          ),
        ),
      ),
    );
  }
}

MainActivity.kt

import com.convex.bnotify_flutter.BNotifyFlutterActivity

class MainActivity : BNotifyFlutterActivity()

Files to place

Path Source
assets/bnotify-config.json Console Android download
android/app/bnotify-config.json Same JSON
ios/Runner/bnotify-config.plist Console iOS download

After integration, send a test campaign from https://console.bnotify.com/.


Package example

See example/. Replace placeholder credentials in:

  • example/assets/bnotify-config.json
  • example/android/app/bnotify-config.json
  • example/ios/Runner/bnotify-config.plist