Flutter SDK
Cross-platform Flutter SDK for real-time push notifications, in-app messages, inbox, A/B testing, and analytics. No Firebase dependency.
Real-time delivery on Android & iOS via persistent connection
Modal, banner, fullscreen layouts triggered by events
Persistent message center with read / archive / unread count
Experiment variant assignment with delivery tracking
Broadcast to interest groups or deliver data-only payloads
Exponential backoff handles flaky networks
Cold-start initial message + foreground tap callback
Full control over your push infrastructure; optional FCM bridge on Android
Installation#
AbrovaPush installs from Abrova Center in two steps.
- Add the package to
pubspec.yaml, pointing at Abrova Center:
dependencies:
abrova_push:
hosted: https://center.abrova.ir/api/packages/abrova/pub
version: ^0.1.20- Fetch the package:
flutter pub get
Android repository
The plugin uses the native Android SDK. Add the Abrova Center repository in android/build.gradle (or android/settings.gradle, wherever your project lists repositories):
allprojects {
repositories {
google()
mavenCentral()
maven { url "https://center.abrova.ir/api/packages/abrova/maven" }
}
}iOS native SDK
The plugin uses the native iOS SDK. Add it inside the app target in ios/Podfile, then run pod install in ios/:
pod 'AbrovaPushSDK', :git => 'https://github.com/Abrova-co/abrova-push-ios-sdk.git', :tag => '0.1.16'
Android Manifest
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
iOS Capabilities
In Xcode → Signing & Capabilities enable:
- Push Notifications
- Background Modes → Remote notifications
Quick Start#
Initialize AbrovaPush before runApp:
import 'package:flutter/material.dart';
import 'package:abrova_push/abrova_push.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AbrovaPush.init(AbrovaPushConfig(
apiKey: 'ab_live_your_api_key',
));
runApp(const MyApp());
}Register and Receive Messages#
@override
void initState() {
super.initState();
// Listen for messages
AbrovaPush.onMessage((message) {
// message.title, message.body, message.data
});
// Connection status
AbrovaPush.onConnectionState((connected) {
// true / false
});
// Register the device
AbrovaPush.register(userId: 'user_123');
}Notification Tap Handling#
When the app cold-starts from a notification tap, fetch the initial message before showing UI:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AbrovaPush.init(config);
final initialMessage = await AbrovaPush.getInitialMessage();
if (initialMessage != null) {
runApp(MyApp(initialRoute: routeFor(initialMessage)));
} else {
runApp(const MyApp());
}
}For taps that happen while the app is running, listen via callback:
AbrovaPush.onNotificationTapped((AbrovaPushMessage message) {
// Navigate based on message.data
});User Identification#
// Set user ID after login
await AbrovaPush.setUserId('user_123');
// Clear user ID after logout
await AbrovaPush.clearUserId();
// Register with user ID + metadata in one call
await AbrovaPush.register(
userId: 'user_123',
metadata: {'appVersion': '1.0.0', 'language': 'en'},
);Topics#
await AbrovaPush.subscribeTopic('news');
await AbrovaPush.subscribeTopic('promotions');
await AbrovaPush.unsubscribeTopic('promotions');In-App Messages#
Trigger in-app messages on app open or custom events. To display them, wrap your app in AbrovaPushInAppOverlay; the native SDKs do not render in-app UI for Flutter (see In-App Messages):
// On app open
await AbrovaPush.triggerInAppOnAppOpen();
// On custom event with properties
await AbrovaPush.triggerInAppEvent('purchase_complete', properties: {
'amount': 29.99,
});
// Button taps on overlay-rendered messages arrive in the overlay's onButtonTap
MaterialApp(
builder: (context, child) => AbrovaPushInAppOverlay(
onButtonTap: (context, message, button) {
// User tapped an action button
},
child: child ?? const SizedBox.shrink(),
),
);AbrovaPushInAppOverlay listens through AbrovaPush.onInAppMessageReady, which holds a single callback; setting your own onInAppMessageReady replaces the overlay's listener.
Inbox#
Inbox messages are stored server-side (sent from Abrova Console or the Inbox API, separately from pushes) so users can revisit them:
// Fetch inbox messages
final response = await AbrovaPush.getInboxMessages(
status: InboxMessageStatus.unread,
limit: 50,
);
// Mark as read
await AbrovaPush.markInboxMessageAsRead(messageId);
// Archive
await AbrovaPush.archiveInboxMessage(messageId);
// Unread count for badges
final count = await AbrovaPush.getInboxUnreadCount();
// Listen for new inbox messages
AbrovaPush.onInboxMessage((message) {
// message.content.title, message.content.body
});A/B Testing#
// Get active tests
final tests = await AbrovaPush.getActiveABTests();
// Get the assigned variant for a test
final variant = await AbrovaPush.getABTestVariant('test_id');
if (variant != null) {
// variant.variantName, variant.content
}
// Track events
await AbrovaPush.trackABTestImpression('test_id', 'variant_id');
await AbrovaPush.trackABTestClicked('test_id', 'variant_id');
// Listen for new variant assignments
AbrovaPush.onABTestVariantAssigned((variant) {
// Newly assigned to a variant
});Analytics#
Forward SDK events to your analytics service:
AbrovaPush.setAnalyticsHandler((event, properties) {
// Example: forward to Firebase Analytics
FirebaseAnalytics.instance.logEvent(
name: 'abrova_push_${event.name}',
parameters: properties?.map((k, v) => MapEntry(k, v.toString())),
);
});Log Levels#
import 'package:flutter/foundation.dart';
if (kReleaseMode) {
await AbrovaPush.setLogLevel(AbrovaPushLogLevel.error);
} else {
await AbrovaPush.setLogLevel(AbrovaPushLogLevel.debug);
}Available levels: none, error, warning, info, debug, verbose.
Configuration Options#
await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', notificationIcon: 'ic_notification', usePushKit: false, showServiceNotification: true, showNotificationInForeground: true, ));
| Option | Type | Required | Description |
|---|---|---|---|
| apiKey | String | Yes | Your project API Key from the dashboard |
| notificationIcon | String? | No | Android notification icon resource name |
| usePushKit | bool | No | Enable iOS PushKit VoIP for calling apps (default: true) |
| showServiceNotification | bool | No | Show Android foreground-service notification (default: true) |
| showNotificationInForeground | bool | No | Show notifications when the app is in foreground (default: true) |
| autoConnect | bool | No | Auto-connect when the app enters the foreground (default: true) |
| autoRefresh | bool | No | Refresh a previously registered device in the background on launch (default: true) |
| appGroup | String? | No | iOS only. App Group shared with a Notification Service Extension, for delivery confirmation (see iOS Delivery Confirmation below) |
Connection configuration is fetched from the server using your API key. No manual setup is needed.
VoIP Calls (Optional)#
For apps with calling features, add the companion VoIP plugin for native incoming-call UI:
dependencies:
abrova_push:
hosted: https://center.abrova.ir/api/packages/abrova/pub
version: ^0.1.20
abrova_push_voip: # Optional - for calling apps
hosted: https://center.abrova.ir/api/packages/abrova/pub
version: ^0.1.1await AbrovaPushVoIP.init( config: AbrovaPushVoIPConfig(appName: 'MyApp'), onCallAccepted: (callData) => navigateToCallScreen(callData), onCallDeclined: (callData) => notifyCallDeclined(callData.callId), ); // Register VoIP after AbrovaPush has issued a deviceId final deviceId = await AbrovaPush.getDeviceId(); await AbrovaPushVoIP.setApiKey(apiKey: 'ab_live_your_api_key', deviceId: deviceId); // deviceId is optional
Trigger an incoming call from the backend with type: "voip_call":
{
"title": "Incoming Call",
"body": "John Doe is calling",
"data": {
"type": "voip_call",
"callerName": "John Doe",
"callerId": "user_456",
"callType": "video"
}
}iOS Delivery Confirmation (Optional)#
Optional, iOS only. Android confirms delivery on its own — nothing to do
there. On iOS, APNs only reports that Apple accepted a notification, so
without this step iOS devices report sent and never delivered.
Confirming delivery on iOS requires a Notification Service Extension, which is a native Xcode target. Follow the full walkthrough in the iOS SDK guide — adding the target, enabling an App Group on both targets, and adding the SDK to the extension target.
From Flutter, the only change is passing the same App Group to the config:
await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', // iOS only. Must match the App Group enabled on BOTH your Runner target and // the Notification Service Extension target. appGroup: 'group.com.yourcompany.yourapp', ));
appGroup is ignored on Android, so it is safe to pass unconditionally.
It appears in four places: both targets' capabilities, your extension's Swift
code, and AbrovaPushConfig. A mismatch fails silently — the extension runs,
finds no device identity, and reports nothing.
API Reference#
AbrovaPush — Initialization & Lifecycle
| Option | Type | Required | Description |
|---|---|---|---|
| init(config) | - | - | Initialize the SDK with an AbrovaPushConfig |
| register(userId?, metadata?) | - | - | Register the device with the Abrova backend |
| unregister() | - | - | Unregister the device |
| isConnected() | - | - | Check whether the real-time connection is open |
| getDeviceId() | - | - | Get the current device identifier |
Callbacks
| Option | Type | Required | Description |
|---|---|---|---|
| onMessage(callback) | - | - | Called when a push message is received |
| onConnectionState(callback) | - | - | Connection open / closed state changes |
| onRegistered(callback) | - | - | Device registration completed |
| onNotificationTapped(callback) | - | - | User tapped a notification while app is running |
| onError(callback) | - | - | String error message |
| onDetailedError(callback) | - | - | Structured AbrovaPushError with code + message |
| onReconnecting(callback) | - | - | Reconnection attempt state |
| onNetworkState(callback) | - | - | Network availability change |
| onAppState(callback) | - | - | App foreground / background change |
User & Topics
| Option | Type | Required | Description |
|---|---|---|---|
| setUserId(id) | - | - | Set the current user identifier |
| clearUserId() | - | - | Clear the user identifier |
| subscribeTopic(topic) | - | - | Subscribe to a topic |
| unsubscribeTopic(topic) | - | - | Unsubscribe from a topic |
Inbox
| Option | Type | Required | Description |
|---|---|---|---|
| getInboxMessages(status?, limit?) | - | - | Fetch a page of inbox messages |
| markInboxMessageAsRead(id) | - | - | Mark an inbox message as read |
| archiveInboxMessage(id) | - | - | Archive an inbox message |
| getInboxUnreadCount() | - | - | Get the unread count for badges |
| onInboxMessage(callback) | - | - | Listen for new inbox messages |
In-App Messages
| Option | Type | Required | Description |
|---|---|---|---|
| triggerInAppOnAppOpen() | - | - | Trigger any in-app message registered for app-open |
| triggerInAppEvent(event, properties?) | - | - | Trigger in-app messages registered for a custom event |
| onInAppMessageReady(callback) | - | - | An in-app message matched a trigger (used by AbrovaPushInAppOverlay) |
| onInAppButtonClick(callback) | - | - | Button tap on a natively rendered message; overlay taps go to AbrovaPushInAppOverlay.onButtonTap |
A/B Testing
| Option | Type | Required | Description |
|---|---|---|---|
| getActiveABTests() | - | - | Get active A/B tests |
| getABTestVariant(testId) | - | - | Get the assigned variant for a test |
| trackABTestImpression(testId, variantId) | - | - | Track an impression |
| trackABTestClicked(testId, variantId) | - | - | Track a click conversion |
| onABTestVariantAssigned(callback) | - | - | Listen for new variant assignments |
Best Practices#
Call WidgetsFlutterBinding.ensureInitialized() then await AbrovaPush.init() before runApp so callbacks are ready
Always await getInitialMessage() before showing your first screen so deep-link taps land on the right route
Call setUserId() once authentication completes so notifications target the right account across devices
Topics suit large audiences; use for newsletters, sports scores, news categories
Segments evaluate user metadata server-side — better for product launches and re-engagement
Wire setAnalyticsHandler to your analytics provider so SDK events (messages, connection, in-app, inbox, A/B tests) show up next to your other product metrics