Prerequisites
- Minimum Supported OS Version: The SDK supports a minimum Android 5.0 Lollipop (API 21) and iOS 15.
- Environment: Minimum Flutter version: 3.3.0, Dart: 3.6.0
Installation
Add the dependency in pubspec.yaml:
dependencies:
salesmanago_mobile_push: 1.2.0
or via command line:
flutter pub add salesmanago_mobile_push
and run installation:
flutter pub get
What's new in 1.2.0
- support for
newEmailin contact data updates - external events support (
addExternalEvent,updateExternalEvent,deleteExternalEvent) - runtime In-App queue interval configuration with
configure(inAppQueueInterval: ...) - init callback result support with
initWithResult() - opt-in Android notification rendering improvements:
- dedicated notification icon via manifest meta-data
BigTextStylefallback when rich image is not available- SALESmanago/non-SALESmanago payload split in
SalesmanagoMessagingService(opt-in)
- under-the-hood native SDK improvements inherited by Flutter plugin:
- improved persisted request queue reliability
- improved retry behavior and queue recovery after connectivity changes
- improved push/deep link and In-App queue handling stability
Initialization
All the methods available in the SDK are accessible via Salesmanago singleton class.
import 'package:salesmanago_mobile_push/sales_manago.dart';
In order to initialize SALESmanago SDK call init() method passing API key.
This method must be called at the app startup.
Salesmanago.instance.init(<API key>);
If you want to observe initialization status, use initWithResult():
final initResult = await Salesmanago.instance.initWithResult(<API key>);
if (initResult.isSuccess) {
// SDK initialized successfully
} else {
// Initialization failed
// initResult.errorMessage contains details when available
}
API key can be obtained from your SALESmanago dashboard.
Runtime reinitialization
The SDK supports runtime reinitialization with a new API key. This allows switching between different SALESmanago API keys during the application's lifecycle without restarting the app.
The SDK switches to the new API key and reinitializes all necessary components and performs an initial synchronization.
All pending requests queued with the previous API key are sent before the API key swap. The SDK maintains contact IDs per API key. If an ID was previously associated with the API key, it will be reused.
To reinitialize the SDK with a new API key, call the reInit() method:
Salesmanago.instance.reInit(<new API key>);
Configuration
There is a number of contact properties which can be set up with an SDK:
- Contact data (name, email address, new email address, phone number, userId, company, address fields, birthday, standard details, double opt-in configuration, number and date details)
- Marketing consents (email, mobile, monitoring)
- Additional custom consents
- List of tags
They can be all set with a single method:
import 'package:salesmanago_mobile_push/model/additional_consent.dart';
import 'package:salesmanago_mobile_push/model/contact_data.dart';
import 'package:salesmanago_mobile_push/model/contact_state.dart';
import 'package:salesmanago_mobile_push/model/double_opt_in.dart';
import 'package:salesmanago_mobile_push/model/marketing_consents.dart';
import 'package:salesmanago_mobile_push/model/opt_in_option.dart';
Salesmanago.instance.updateContactProperties(
contactData: ContactData(
name: 'John Doe',
email: 'john.doe@email.com',
newEmail: 'john.new@email.com',
phone: '+48123456789',
userId: 'user-123',
company: 'Example Inc',
state: ContactState.customer,
birthday: 19900101,
streetAddress: 'Main Street 10/2',
city: 'Cracow',
zipCode: '30-001',
province: 'Lesser Poland',
country: 'Poland',
doubleOptIn: DoubleOptIn(
emailId: 'f40bb45d-83b3-4e40-8211-b04e5faa0458',
language: 'EN',
),
standardDetails: {
'first_detail': 'first_value',
'second_detail': 'second_value',
},
numberDetails: {
'purchasesCount': 12,
},
dateDetails: {
'lastPurchase': 1696118400000,
},
),
marketingConsents: MarketingConsents(
email: OptInOption.granted,
mobile: OptInOption.granted,
monitoring: OptInOption.denied,
),
additionalConsents: [
AdditionalConsent(
name: 'custom consent',
status: OptInOption.denied,
),
],
tagsToAdd: ['tag_to_add'],
tagsToRemove: ['tag_to_remove'],
);
OptInOption is an SDK enum class representing possible values for consents:
granted- Contact has given the consentdenied- Contact has rejected or withdrawn the consentnoAnswer- Contact has neither given nor rejected the consent. The status will not change. If there was no status, the consent will be set as rejected.
ContactState is an SDK enum class with the following values:
customerprospectpartnerotherunknown
All parameters are optional so this method can be used to set just some of them. However there are also dedicated methods which can be used to set selected type of contact properties.
Contact data:
Salesmanago.instance.updateContactData(
name: 'John Doe',
email: 'john.doe@email.com',
newEmail: 'john.new@email.com',
phone: '+48123456789',
userId: 'user-123',
company: 'Example Inc',
state: ContactState.customer,
birthday: 19900101,
streetAddress: 'Main Street 10/2',
city: 'Cracow',
zipCode: '30-001',
province: 'Lesser Poland',
country: 'Poland',
doubleOptIn: DoubleOptIn(
emailId: 'f40bb45d-83b3-4e40-8211-b04e5faa0458',
language: 'EN',
),
standardDetails: {
'first_detail': 'first_value',
'second_detail': 'second_value',
},
numberDetails: {
'purchasesCount': 12,
},
dateDetails: {
'lastPurchase': 1696118400000,
},
);
Marketing consents:
Salesmanago.instance.updateContactMarketingConsents(
email: OptInOption.denied,
mobile: OptInOption.noAnswer,
monitoring: OptInOption.granted,
);
Additional custom consents:
Salesmanago.instance.updateContactAdditionalConsents([
AdditionalConsent(
name: 'some custom consent',
status: OptInOption.granted,
),
]);
Tags:
Salesmanago.instance.addTags(['only_tag_to_add']);
Salesmanago.instance.removeTags(['only_tag_to_remove']);
Set as null:
Salesmanago.instance.setAsNull(['phone']);
Events
SALESmanago SDK enables to track user activity by sending pre-defined events:
import 'package:salesmanago_mobile_push/model/event_type.dart';
Salesmanago.instance.addEvent(EventType.login);
EventType is an SDK enum class representing possible event types. Currently following types are available:
login- user logs into the application
External events
Track external events with transaction details.
External events can be recorded only for contacts with an e-mail.
While adding a new external event, the SDK generates an eventId (UUID) and returns it for further managing the event via other methods.
import 'package:salesmanago_mobile_push/model/external_event_type.dart';
final eventId = await Salesmanago.instance.addExternalEvent(
eventTime: '1772700397',
eventType: ExternalEventType.purchase,
products: ['product1', 'product2', 'product3'],
value: 99.99,
location: 'shop123',
externalId: 'order-456',
detail1: 'Payment method: Credit Card',
detail2: 'Shipping: Express',
detail3: 'Discount: 10%',
detail4: 'Customer segment: Premium',
detail5: 'Campaign: Summer Sale',
description: 'Example purchase transaction',
);
You can update or delete the event via updateExternalEvent and deleteExternalEvent methods.
To specify the event for update, use eventId returned from the addExternalEvent method.
Salesmanago.instance.updateExternalEvent(
eventId: 'fe964947-23b6-4950-a8a0-1848c20baf78',
eventTime: '1772700397',
eventType: ExternalEventType.cancellation,
products: ['product1', 'product2', 'product3'],
value: 1.0,
detail1: 'Updated detail',
description: 'Updated description',
);
Method deleteExternalEvent uses eventId or externalId to identify the event.
At least one of these parameters has to be specified. If you specify both, the eventId takes priority.
Salesmanago.instance.deleteExternalEvent(
eventId: 'fe964947-23b6-4950-a8a0-1848c20baf78',
externalId: 'fe964947-23b6-4950-a8a0-1848c20baf78',
);
Push notifications
Push notifications are sent via Firebase Cloud Messaging on Android and via APNs on iOS.
Your app has to ask a user to permit showing notifications. Without it, the SDK will not be allowed to show the notifications.
After the user grants the permission, request the SDK to update its status via onPushNotificationSystemPermissionsChanged() method.
Additionally, there is a separate method to set the contact's opt-in status for receiving in-app marketing push notifications:
Salesmanago.instance.updateMobilePushOptIn(OptInOption.granted);
iOS
Additional configuration for iOS is only required for deep links handling.
The deep link scheme has to be declared in your Info.plist file.
For instance, if you create a notification with the deep link salesmanago://salesmanago.com/main in the SM dashboard, your Info.plist file should contain:
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>YOUR APP BUNDLE IDENTIFIER</string>
<key>CFBundleURLSchemes</key>
<array>
<string>salesmanago</string>
</array>
</dict>
</array>
You can also specify it in Xcode:
In your application target, open Info tab. In URL Types section click + and enter required data:
- Identifier: your app bundle identifier
- URL scheme: your unique scheme, in this example
salesmanago
Now your Flutter application will receive the deeplink main in the navigation configuration:
MaterialApp(
onGenerateRoute: (settings) {
settings.name // this variable should contain 'main'
);
},
);
Note:
The iOS part of the Flutter framework treats deep links like URLs - the navigation works on paths and begins after scheme and host.
For instance, the deep linksalesmanago://salesmanago.com/mainwill be truncated to just/mainin the Flutter navigation.
When creating your deep links, be sure to include the host before path to navigate.On the other side, on Android, Flutter receives the full deep link URL (with the scheme and host).
iOS: Push routing with multiple providers
When your app uses multiple push providers, route only SALESmanago payloads to the SDK. No separate mode is required on iOS.
If your app extends FlutterAppDelegate, two valid split patterns are common:
- non-SALESmanago -> your own flow or
super, SALESmanago ->Salesmanago.didReceiveRemoteNotification(...) - SALESmanago ->
super, non-SALESmanago -> your own flow (sample app pattern)
In both patterns, keep the same payload split with Salesmanago.isSdkNotification(...) and call completionHandler exactly once per notification.
If you want to receive non-SALESmanago UNUserNotificationCenter callbacks (willPresent / didReceive),
register a downstream notification delegate in your AppDelegate:
Salesmanago.setNotificationDelegate(self)
If your AppDelegate extends FlutterAppDelegate, use this pattern (forward non-SALESmanago payloads to super):
public override func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
guard Salesmanago.isSdkNotification(userInfo: userInfo) else {
// Handle non-SALESmanago payload in your own push flow.
super.application(
application,
didReceiveRemoteNotification: userInfo,
fetchCompletionHandler: completionHandler
)
return
}
Salesmanago.didReceiveRemoteNotification(userInfo: userInfo)
completionHandler(.noData)
}
If you use your own non-SALESmanago push flow (without forwarding to super), keep the same split but ensure the non-SALESmanago path calls completionHandler exactly once in your own code:
public override func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
guard Salesmanago.isSdkNotification(userInfo: userInfo) else {
// Handle non-SALESmanago payload and call completionHandler once in this flow.
handleNonSalesmanagoPayload(userInfo, completionHandler: completionHandler)
return
}
Salesmanago.didReceiveRemoteNotification(userInfo: userInfo)
completionHandler(.noData)
}
Do not call completionHandler on both paths for the same notification.
Android
All the Firebase management work is done inside the SDK. The only thing to do is to connect the app with your Firebase project via JSON file generated in your Firebase project dashboard.
Paste your google-services.json file into the android app-level root directory (/android/app).
Next, add the Google services Gradle plugin in your android project-level build.gradle file (/android/build.gradle):
plugins {
// dependency for the Google services Gradle plugin
id("com.google.gms.google-services") version "<version>" apply false
}
and in the android app-level build.gradle file ((/android/app/build.gradle):
plugins {
id("com.android.application")
// Google services Gradle plugin
id("com.google.gms.google-services")
...
}
If you want to use deep links in your push notifications, you have to declare it in your app.
The AndroidManifest.xml file declares your main activity (usually MainActivity) in <activity> tag. To make this activity able to handle your deep links, you should declare your scheme in a separate intent-filter.
For instance, if you create a notification with the deep link salesmanago://salesmanago.com/main in the SM dashboard, your <activity> tag should contain:
<activity
...
...
<intent-filter>
...
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="salesmanago" />
</intent-filter>
</activity>
Now your Flutter application will receive the deeplink salesmanago://salesmanago.com/main in the navigation configuration:
MaterialApp(
onGenerateRoute: (settings) {
settings.name // this variable should contain 'salesmanago://salesmanago.com/main' on notification click
);
},
);
Android: Default SDK flow
In the default setup, you do not need any AndroidManifest changes for push handling.
The native SDK auto-registers its default MessagingService.
Android: Optional custom service
Use this only when you need custom Android notification rendering or multi-provider split routing.
Option A: use plugin-provided SalesmanagoMessagingService and replace the default SDK FCM service in your app AndroidManifest.xml:
<manifest xmlns:tools="http://schemas.android.com/tools" ...>
<application>
<service
android:name="com.salesmanago.library.common.messaging.MessagingService"
tools:node="remove" />
<service
android:name="com.salesmanago.mobilepush.SalesmanagoMessagingService"
android:exported="false"
tools:node="replace">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
SalesmanagoMessagingService handles only SALESmanago payloads (Salesmanago.isSdkNotification(data)) and ignores non-SALESmanago payloads.
Use this option only when you do not need custom handling for non-SALESmanago payloads.
If you need to process non-SALESmanago pushes, use Option B with your own FirebaseMessagingService.
To use a dedicated notification icon for Android notifications, add this meta-data entry inside your app <application> tag:
<meta-data
android:name="com.salesmanago.mobilepush.notification.smallIcon"
android:resource="@drawable/ic_stat_salesmanago" />
If the meta-data is not provided, the SDK falls back to the application icon.
For best visibility on Android status bar, use a monochrome notification icon (white glyph on transparent background).
Rendering style rules:
- if push payload contains a valid
imageUrl, notification is shown usingBigPictureStyle - otherwise, notification is shown using
BigTextStylewith the push content
Option B: register your own FirebaseMessagingService for multi-provider apps.
Route SALESmanago payloads using Salesmanago.isSdkNotification(data) and forward only these payloads to SDK handling.
To enable this option, replace the plugin service in your app AndroidManifest.xml:
<manifest xmlns:tools="http://schemas.android.com/tools" ...>
<application>
<service
android:name="com.salesmanago.library.common.messaging.MessagingService"
tools:node="remove" />
<service
android:name="com.example.app.CustomAppMessagingService"
android:exported="false"
tools:node="replace">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
Example split in your custom service:
class CustomAppMessagingService : FirebaseMessagingService() {
private val smService = MessagingService(this)
override fun onMessageReceived(message: RemoteMessage) {
if (Salesmanago.isSdkNotification(message.data)) {
smService.onMessageReceived(message)
} else {
// Handle non-SALESmanago provider payload.
}
}
override fun onNewToken(token: String) {
smService.onNewToken(token)
}
}
In-App
For your app to display in-apps properly, your Android main activity should not block the affinity.
It is usually done in the AndroidManifest.xml file under the <activity> tag via the attribute android:taskAffinity.
Ensure your activity does not have an attribute android:taskAffinity="".
Queue Interval
The SDK enforces a minimum delay between consecutive in-app message displays (default: 60 seconds). You can configure this interval at runtime using the configure() method. The value is specified in seconds.
Salesmanago.instance.configure(inAppQueueInterval: 5);
Libraries
- internal/platform_data_mapper
- internal/sales_manago_method_channel
- internal/sales_manago_platform
- model/additional_consent
- model/contact_data
- model/contact_state
- model/double_opt_in
- model/event_type
- model/external_event_type
- model/marketing_consents
- model/opt_in_option
- model/sdk_init_result
- sales_manago