bnotify_flutter 0.1.2
bnotify_flutter: ^0.1.2 copied to clipboard
BNotify Flutter plugin wrapping the native Android and iOS SDKs for push notifications and in-app messaging.
bnotify_flutter #
Flutter plugin for BNotify push notifications and in-app messaging.
Install: bnotify_flutter: ^0.1.2 on pub.flutter-io.cn
Wraps the native SDKs:
- Android: JitPack
bnotifysdk1.0.5 - iOS: SPM
BNotify
Web is not included. The plugin Git repository is private (not open source). Install from pub.flutter-io.cn 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_flutter0.1.2
Platforms: Android + iOS (web is not supported)
Console: https://console.bnotify.com/
Native Android SDK (pulled in automatically): JitPackbnotifysdk1.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 #
- What you get after integration
- Prerequisites
- Integration checklist
- Step 1 — Console: create the project and apps
- Step 2 — Add the Flutter package
- Step 3 — Dart: initialize, permission, and listeners
- Step 4 — Android host setup
- Step 5 — iOS host setup
- Step 6 — In-app campaigns (
logEvent) - Step 7 — Notification tap routing
- Optional — Custom Android notification UI
- Verification and test plan
- Troubleshooting
- Production checklist
- FAQ
- 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.
- In the Console, create a project and register Android and iOS apps with the exact
applicationId/ bundle identifier of this Flutter app. - Download
bnotify-config.jsonandbnotify-config.plist. - Add
bnotify_flutter: ^0.1.2inpubspec.yamlfrom pub.flutter-io.cn. - Declare
assets/bnotify-config.jsoninpubspec.yamland keep a copy atandroid/app/bnotify-config.json. - Add JitPack, set
minSdk24, extendBNotifyFlutterActivity, setsingleTask, switch themes to AppCompat. - Add
ios/Runner/bnotify-config.plistto the Runner target, enable Push Notifications, optionally add a Notification Service Extension. - In Dart:
WidgetsFlutterBinding.ensureInitialized(), subscribe to streams,await BNotify.initialize(...), thenrequestPermission(). - 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 #
- Open https://console.bnotify.com/.
- Create a project (or pick an existing one).
- Register an Android app whose package name equals your Flutter
applicationId(seeandroid/app/build.gradle.kts→defaultConfig.applicationId). - Register an iOS app whose bundle id equals
ios/Runner’s bundle identifier. - Download:
bnotify-config.jsonfor Androidbnotify-config.plistfor 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.flutter-io.cn.
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.flutter-io.cn.
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 #
- Copy the Console plist to
ios/Runner/bnotify-config.plist. - In Xcode, select the file → File Inspector → Target Membership → enable Runner.
- Confirm
packageNamein 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 (
appGroupIdif present).
8.3 Swift Package Manager (recommended with current Flutter) #
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):
- File → New → Target → Notification Service Extension.
- Add the BNotify iOS SDK to that extension target (Swift Package:
https://gitlab.com/hamza.hafeez/bnotifyiossdk.git, productBNotify). - Enable the same App Group on Runner and the extension.
- 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) #
- In the Console, create an in-app campaign whose trigger name matches a string you will log (for example
app_open). - 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.flutter-io.cn 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/.pliston the device (not placeholders) - ❌ Those files are not in a public git repo
- ❌
bnotify_flutter: ^0.1.2from pub.flutter-io.cn - ❌
minSdk24, JitPack present - ❌
MainActivityextendsBNotifyFlutterActivity - ❌
launchMode="singleTask", no emptytaskAffinity - ❌ AppCompat launch/normal themes
- ❌ Android config copied into
src/main/assets - ❌ iOS Push capability (and NSE if you send images)
- ❌
initialize+requestPermissionin release builds - ❌
onClickrouting tested from a killed app - ❌ Release signing / APNs production setup as required by Apple and Play
15. FAQ #
Is this on pub.flutter-io.cn?
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.jsonexample/android/app/bnotify-config.jsonexample/ios/Runner/bnotify-config.plist