Adstart Flutter SDK
Flutter wrapper for Adstart event collection.
The SDK sends session, screen, sign-up, logout, customer identification,
catalog, search, wishlist, cart, checkout, purchase, engagement, and custom
events. It also retrieves the App Push history for the current device. It
persists a deviceId across app launches and creates a new sessionId for
every call to init().
Installation
Add the package from pub.dev:
flutter pub add adstart_flutter_sdk
Or add it directly to pubspec.yaml:
dependencies:
adstart_flutter_sdk: ^0.1.6
Then run flutter pub get.
Installation from Git
To test an unreleased branch, use the Git repository:
dependencies:
adstart_flutter_sdk:
git:
url: https://github.com/adstart-team/adstart-flutter-sdk.git
ref: branch-or-commit
Usage
Configure and initialize the Firebase project for the current business before
calling AdstartSdk.init(). Follow the FlutterFire setup for Android and iOS,
including the APNs configuration required by Firebase Cloud Messaging on iOS.
import 'package:firebase_core/firebase_core.dart';
import 'package:adstart_flutter_sdk/adstart_flutter_sdk.dart';
await Firebase.initializeApp();
final adstart = AdstartSdk(hashedId: 'your-hashed-id', debug: true);
final sessionId = await adstart.init(
onPushEvent: (event) {
switch (event.type) {
case AdstartPushEventType.received:
// The app owns foreground presentation, if any.
debugPrint('Push received: ${event.notification?.title}');
case AdstartPushEventType.opened:
// Forward the destination to Navigator, go_router, or another router.
final deepLink = event.deepLink;
if (deepLink != null) {
debugPrint('Open $deepLink');
}
}
},
);
await adstart.addProductToWishlist(
WishlistProductEventParams(
currency: 'BRL',
items: [
AdstartItem(
itemId: 'product-123',
itemName: 'Product name',
price: 249.90,
),
],
source: 'product_detail',
),
);
await adstart.removeProductFromWishlist(
WishlistProductEventParams(
currency: 'BRL',
items: [AdstartItem(itemId: 'product-123', price: 249.90)],
),
);
await adstart.viewWishlist(
const WishlistViewEventParams(source: 'account_menu'),
);
await adstart.addProductToCart(
EcommerceEventParams(
currency: 'BRL',
items: [AdstartItem(itemId: 'product-123', price: 249.90)],
),
);
await adstart.removeProductFromCart(
EcommerceEventParams(
currency: 'BRL',
items: [AdstartItem(itemId: 'product-123', price: 249.90)],
),
);
final cart = EcommerceEventParams(
currency: 'BRL',
items: [
AdstartItem(itemId: 'product-123', price: 249.90, quantity: 2),
],
);
await adstart.viewCart(cart);
final checkout = CheckoutEventParams(
currency: 'BRL',
items: cart.items,
coupon: 'WELCOME10',
);
await adstart.addPaymentInfo(checkout);
await adstart.addShippingInfo(checkout);
await adstart.initiateCheckout(checkout);
await adstart.purchase(
PurchaseEventParams(
currency: checkout.currency,
items: checkout.items,
coupon: checkout.coupon,
orderId: 'order-123',
shippingValue: 19.90,
),
);
await adstart.productViewed(
EcommerceEventParams(
currency: 'BRL',
items: [
AdstartItem(
itemId: 'product-123',
itemName: 'Product name',
price: 249.90,
itemBrand: 'Brand name',
itemCategory: 'Category name',
),
],
),
);
await adstart.categoryViewed(name: 'Category name');
await adstart.brandViewed(name: 'Brand name');
await adstart.searchedFor(searchTerm: 'black shoes');
await adstart.screenVisited(
ScreenEventParams(
name: 'product_list',
attributes: <String, Object?>{
'filters': <Map<String, Object?>>[
<String, Object?>{
'field': 'brand',
'operator': 'equals',
'value': 'Nike',
},
<String, Object?>{
'field': 'price',
'operator': 'between',
'value': <num>[100, 500],
},
],
'source': 'home',
},
),
);
await adstart.signUp();
await adstart.logout();
await adstart.trackTimeOnPage(TimeOnPageEventParams(seconds: 45));
await adstart.trackScrollDepth(ScrollDepthEventParams(depth: 50));
await adstart.customerIdentified(
CustomerIdentificationEventParams(
email: 'customer@example.com',
customerExternalId: 'customer-123',
gender: CustomerGender.other,
),
);
await adstart.customerUpdated(
<String, Object?>{
'birthDate': '1990-01-01',
'loyalty': <String, Object?>{
'tier': 'gold',
'points': 1200,
},
'favoriteStore': null,
},
);
await adstart.trackCustomEvent(
'product_compared',
eventParams: <String, Object?>{
'productIds': <String>['product-123', 'product-456'],
'source': 'comparison_screen',
},
);
adstart.dispose();
Call init() before sending events. All events sent after
initialization reuse the active session's hashedId, deviceId, sessionId,
and environment metadata.
Sign-up event
Call signUp() after the application successfully completes a new account
registration. It emits the standard Adstart sign_up event with empty
eventParams and no eventKey.
The Adstart event ingestion contract must include the native sign_up event
type before production can accept this event. Sign-up remains distinct from
login and customer identification.
Logout event
Call logout() after the application successfully completes a user logout. It
emits the standard Adstart logout event with empty eventParams and no
eventKey. It does not revoke push permissions, delete the FCM token, or end
the active SDK session.
The Adstart event ingestion contract must include the native logout event
type before production can accept this event.
Ecommerce and catalog events
AdstartItem is the shared public item model for wishlist, cart, checkout,
purchase, and product view events. Each item requires a finite, non-negative
price, a positive quantity that defaults to 1, and at least one non-empty
itemId or itemName. It also supports affiliation, item-level coupon and
discount, position, brand, five category levels, list information, variant, and
location.
EcommerceEventParams requires a non-empty item list and a three-letter
currency code. Currency is trimmed and normalized to uppercase. The SDK derives
the emitted value by summing price * quantity for all items with decimal
accumulation; callers cannot provide a conflicting value.
Use WishlistProductEventParams when the optional source is useful. Use
CheckoutEventParams for payment, shipping, and checkout events; its coupon
is optional and is omitted from the payload when absent or blank.
Use PurchaseEventParams for purchase events. It requires a non-empty
orderId and a finite, non-negative shippingValue, in addition to currency
and items. Its optional coupon is omitted when absent or blank. The SDK keeps
shippingValue separate and derives value exclusively from price * quantity across the items.
The public methods preserve Adstart event names:
| Method | eventType |
Required parameters |
|---|---|---|
addProductToWishlist |
added_to_wishlist |
currency, items |
removeProductFromWishlist |
removed_from_wishlist |
currency, items |
addProductToCart |
added_to_cart |
currency, items |
removeProductFromCart |
removed_from_cart |
currency, items |
viewCart |
view_cart |
currency, items |
addPaymentInfo |
added_payment_info |
currency, items |
addShippingInfo |
added_shipping_info |
currency, items |
initiateCheckout |
checkout_initiated_by |
currency, items |
purchase |
purchase |
currency, items, orderId, shippingValue |
productViewed |
product_viewed |
currency, items |
screenVisited |
page_viewed |
name |
categoryViewed |
category_viewed |
name |
brandViewed |
brand_viewed |
name |
searchedFor |
search |
searchTerm |
signUp |
sign_up |
none |
logout |
logout |
none |
searchedFor emits the term as eventParams.search_term. The Adstart event
ingestion contract must include the search event type before production can
accept this event.
Screen visits
Call screenVisited when the application confirms that a native screen has
been displayed. ScreenEventParams requires a non-empty screen name and
accepts optional dynamic attributes. Attributes are flattened into
eventParams, may contain JSON-compatible nested lists and maps, and cannot use
the reserved name key.
The SDK emits page_viewed but does not install a global observer for
Navigator, go_router, or WebView navigation. The integrating application
owns the moment when a screen visit is reported.
Customer identification
Use customerIdentified after init() to associate identity data with the
current device. At least one non-empty email, phone, or document is
required. name, customerExternalId, and gender are optional. gender,
when provided, is typed as CustomerGender.male, CustomerGender.female,
CustomerGender.other, or CustomerGender.unknown.
Only these canonical identity fields are sent in eventParams; session and
device identifiers remain in the common event envelope.
Customer updates
Use customerUpdated after init() to send customer attributes defined by the
integrating application. The map accepts JSON-compatible values, including
nested maps, lists, numbers, booleans, strings, and null.
The SDK sends the attributes in eventParams with the native
customer_updated event type and no eventKey. The Adstart event ingestion
contract must include customer_updated before production can accept it as a
native event.
Push notifications
init() never opens the notification permission prompt. When permission is
already authorized or provisional, it silently obtains the FCM token and
registers it with Adstart. A missing Firebase configuration, a null token, or a
registration failure does not make a successful session_started fail.
Ask for permission only from an explicit user action:
await adstart.requestPushPermission();
Call init() first so the registration can reuse the SDK's persistent
deviceId and configured hashedId. The token is sent to
POST https://events.adstart.cloud/v1/app-push/register together with the
Firebase appId. Authorized and provisional results are registered. Denied
and not-determined results are not registered.
The SDK observes FirebaseMessaging.onTokenRefresh and synchronizes new tokens.
It stores only a SHA-256 token fingerprint for deduplication, never the complete
token, and debug logs never include the complete token. Call dispose() when
the SDK is no longer used so the refresh listener is canceled.
To delete the token from the local Firebase installation:
await adstart.disablePushNotifications();
In version 0.0.8 this method only calls FirebaseMessaging.deleteToken(). It
does not call a backend revoke endpoint.
Listing push notifications
After init(), call listPushNotifications() to retrieve the App Push
messages sent to the SDK's persistent deviceId:
final notifications = await adstart.listPushNotifications(limit: 20);
for (final notification in notifications) {
debugPrint('${notification.sentAt}: ${notification.title}');
debugPrint(notification.destination);
}
The result is ordered from the most recently sent notification to the oldest.
Each AppPushNotification contains id, title, body, optional imageUrl
and destination, and the UTC-aware sentAt. This history is scoped to the
current business and device; it is not a cross-device customer inbox.
By default, the API returns up to 100 notifications from the previous 30 days. Use an explicit period when needed:
final notifications = await adstart.listPushNotifications(
startDate: DateTime.utc(2026, 7, 1),
endDate: DateTime.utc(2026, 7, 21),
limit: 50,
);
startDate is inclusive, endDate is exclusive, and both must be provided
together. The period cannot exceed 30 days and limit must be between 1 and
100. The SDK sends this request to
GET https://events.adstart.cloud/v1/app-push/notifications with the configured
hashedId and current deviceId.
Receiving and opening push notifications
Pass an optional onPushEvent callback to init() to receive typed push
events. AdstartPushEventType.received is emitted for messages observed while
the app is in the foreground. The SDK does not display these messages; the app
owns any foreground notification UI, Android notification channels, icons, and
iOS presentation options. The normalized event.notification contains the
optional title and body needed for foreground presentation, while event.data
contains the custom FCM data fields.
AdstartPushEventType.opened is emitted when the user opens a notification
from the background or launches the app by opening it from the terminated
state. Openings without a deep link are still emitted with deepLink == null.
The SDK extracts an absolute URI from the adstart_deep_link data field but
never navigates or opens it. The integrating app must validate that the URI is
an allowed destination before forwarding it to its router.
Use these optional FCM data fields:
{
"adstart_message_id": "unique-message-id",
"adstart_deep_link": "posthaus://product/123"
}
adstart_message_id is preferred for deduplicating openings. If it is absent,
the SDK uses the Firebase message ID and then a fingerprint of the message
data.
Background or terminated delivery does not guarantee a received callback.
In those states Android and iOS can display a notification payload without
running the main Flutter isolate. This version does not install a global
FirebaseMessaging.onBackgroundMessage handler. Delivery of data-only
background messages is platform-dependent and outside this callback contract.
Call dispose() when the SDK is no longer used. It cancels the foreground,
opening, and token refresh listeners.
Custom events
Use trackCustomEvent for business events that are not part of the SDK's
built-in event catalog. The eventKey is required, must start with a lowercase
letter, may contain only lowercase letters, numbers, and underscores, and may
contain at most 64 characters. Built-in Adstart event names are reserved.
eventParams is optional and defaults to an empty JSON object. Its encoded JSON
must not exceed 64 KiB in UTF-8.
Screen engagement
Flutter does not expose a reliable global definition of a page or its scroll
depth. The application must measure engagement for each native screen and call
trackTimeOnPage with the elapsed whole seconds (from 1 through 1800) and
trackScrollDepth with a percentage (from 0 through 100). The web tracker uses
the 25, 50, 75, and 100 percent milestones.
Content rendered inside a WebView has its own document lifecycle and scroll position. Measure it in the web content and forward the resulting seconds or depth to these methods; the SDK does not install global navigation, scroll, or WebView listeners.