Skip to main content

datra_engage_sdk 0.1.2 copy "datra_engage_sdk: ^0.1.2" to clipboard
datra_engage_sdk: ^0.1.2 copied to clipboard

Flutter SDK for resolving and tracking Datra Engage in-app messages.

Datra Engage SDK for Flutter #

datra_engage_sdk resolves Datra in-app messages and sends delivery events from Flutter applications. Version 0.1.2 is contract-compatible with @datra/engage-sdk 0.3.2, including bounded per-placement diagnostics and shared resolve reliability.

The SDK does not depend on Riverpod or go_router, does not render marketing UI, and does not control navigation. Your app owns presentation and action handling.

Installation #

During local development:

dependencies:
  datra_engage_sdk:
    path: ../datra-engage-flutter-sdk

After the first public release:

dependencies:
  datra_engage_sdk: ^0.1.2

Then import the package:

import 'package:datra_engage_sdk/datra_engage_sdk.dart';

Release process #

Every change to main and every pull request is validated by the datra-engage-flutter-sdk Azure pipeline on the isolated Datra build agent. Publishing is a separate manual release:

  1. Merge an approved pull request and wait for a successful CI build on the exact release commit.
  2. Create and push a v<version> tag whose version matches pubspec.yaml.
  3. Run Datra Engage Flutter SDK Publish using that tag and provide the successful CI build ID for the same commit.
  4. Select validate to run the complete release gate without publishing, or publish to continue to the protected datra-pubdev-production environment.
  5. Approve the production publication in Azure DevOps.

The release pipeline rejects branches, mismatched tags, failed or unrelated CI builds, and CI builds for another commit. Pub.dev authentication uses a dedicated Google service account stored only as an Azure secret. Release tags and public publication are never created by the normal CI pipeline.

The one-time release setup uses the protected Azure variable group datra-pubdev: pubdev_package contains only the public package name, while gcp_pub_service_account_key_b64 is a secret. The same service-account email must be allowed in the package's pub.dev Admin page. Keep the secret set to the non-working placeholder until that permission is configured.

Configure the client #

Create one client and keep it for the application lifetime. A Datra public SDK key may be embedded in a mobile app. Never place an integration token, admin token, private API key, or other secret in the app.

final engage = DatraEngage(
  const DatraEngageConfig(
    baseUrl: 'https://api.example.com',
    publicKey: 'pk_live_your_public_key',
    customerExternalId: 'customer_123',
    platform: 'flutter',
    appVersion: '1.0.0',
    locale: 'uz',
    defaultContext: {
      'branchExternalId': 'branch_1',
    },
  ),
);

Update identity after login or logout:

engage.identify(
  const DatraEngageIdentity(
    customerExternalId: 'customer_456',
    context: {'segment': 'regular'},
  ),
);

engage.setCustomerExternalId(); // clear customer after logout

identify has patch semantics: non-null identity fields update the current value, while omitted/null fields keep their current value. Its context is merged into the existing context instead of replacing it. Use the explicit nullable setters when a field must be cleared:

engage.setCustomerExternalId(); // clears customer ID and cancels stale resolves
engage.setPlatform();           // clears platform
engage.setAppVersion('');       // empty or whitespace also clears the value
engage.setLocale(null);         // clears locale

Changing the customer cancels obsolete in-flight resolve requests. Changing platform, app version, or locale affects subsequent requests but does not cancel a request already in flight.

Flutter provider and banner placement #

Wrap the relevant subtree. flushEventQueueOnResume is optional.

DatraEngageProvider(
  client: engage,
  flushEventQueueOnResume: true,
  child: const MyApp(),
)

Resolve a banner for a placement and render it with your own design:

DatraEngageBuilder(
  screen: 'home',
  placement: 'home_top_banner',
  enabled: customerId != null,
  customerExternalId: customerId,
  engageContext: {
    'cartTotal': cart.total,
    'productIds': cart.productIds,
  },
  trackShown: true,
  builder: (context, state) {
    if (state.isLoading && state.messages.isEmpty) {
      return const SizedBox.shrink();
    }
    if (state.messages.isEmpty) {
      return const SizedBox.shrink();
    }

    final message = state.messages.first;
    return OfferBanner(
      title: message.title,
      body: message.body,
      imageUrl: message.imageUrl,
      onTap: () async {
        await engage.messageClickedSafe(message.decisionId);
        await handleEngageAction(message.action, message);
      },
      onDismiss: () => engage.messageDismissedSafe(message.decisionId),
    );
  },
)

DatraEngageMessagesState exposes messages, response, isLoading, error, and refresh. Request failures stay inside the state and never throw from the widget lifecycle.

Resolve messages without widgets #

final response = await engage.getMessages(
  const ResolveMessagesInput(
    placement: 'cart_product_reward',
    screen: 'cart',
    context: {
      'cartProductIds': ['fries', 'burger'],
      'loyaltyPoints': 20,
    },
  ),
);

Use getMessagesSafe or screenViewedSafe when Engage must fail open. They return an empty message list on network, timeout, cancellation, or parsing errors.

Tracking #

await engage.messageShownSafe(message.decisionId);
await engage.messageClickedSafe(message.decisionId);
await engage.messageDismissedSafe(message.decisionId);
await engage.messageConvertedSafe(
  message.decisionId,
  const TrackEventOptions(metadata: {'orderExternalId': 'order_123'}),
);

Strict variants (messageShown, messageClicked, messageDismissed, messageConverted, and trackEvent) throw a DatraEngageError. Safe variants return SafeTrackEventResult and do not interrupt application flows.

Default idempotency keys match the React Native SDK:

datra:shown:<decisionId>
datra:clicked:<decisionId>
datra:dismissed:<decisionId>
datra:converted:<decisionId>

Action handling #

The SDK only parses and dispatches actions. The application decides how to navigate.

final handleEngageAction = createDatraEngageActionHandler(
  DatraEngageActionHandlers(
    onOpenScreen: (screen, action, message) {
      router.goNamed(screen);
    },
    onOpenPromotion: (promotionId, action, message) {
      router.push('/promotions/$promotionId');
    },
    onOpenProduct: (productId, action, message) {
      router.push('/products/$productId');
    },
    onOpenCart: (action, message) {
      router.go('/cart');
    },
    onOpenUrl: (url, action, message) {
      openExternalUrl(url);
    },
    onUnhandledAction: (action, message) {
      diagnostics.log('Unsupported Engage action: ${action.type}');
    },
  ),
);

Typed handlers are available for OPEN_SCREEN, OPEN_PROMOTION, OPEN_COUPON, OPEN_PRODUCT, OPEN_CART, OPEN_URL, OPEN_STORY, and OPEN_MECHANIC. Unknown or incomplete actions go to onUnhandledAction.

Offline event queue #

The queue is opt-in. It stores retryable event failures only, keeps at most 100 events by default, and flushes with concurrency 3.

final storage = MemoryDatraEngageStorage();

final engage = DatraEngage(
  DatraEngageConfig(
    baseUrl: 'https://api.example.com',
    publicKey: 'pk_live_your_public_key',
    eventQueue: DatraEngageEventQueueConfig(
      enabled: true,
      storage: storage,
    ),
  ),
);

await engage.flushEventQueue();

Memory storage is the safe default but does not survive process restarts. To persist the queue and dedupe keys, implement DatraEngageStorage in the app with shared_preferences:

class SharedPreferencesEngageStorage implements DatraEngageStorage {
  SharedPreferencesEngageStorage(this.preferences, {this.prefix = ''});

  final SharedPreferences preferences;
  final String prefix;

  @override
  Future<String?> getItem(String key) async =>
      preferences.getString('$prefix$key');

  @override
  Future<void> setItem(String key, String value) async {
    await preferences.setString('$prefix$key', value);
  }

  @override
  Future<void> removeItem(String key) async {
    await preferences.remove('$prefix$key');
  }
}

The SDK intentionally does not require shared_preferences; the host app chooses its persistence implementation.

Runtime defaults #

Operation Timeout Retries
Message resolve 2000 ms 0
Event tracking 3000 ms 1

Retry statuses are 408, 429, 500, 502, 503, and 504. Event dedupe is enabled by default with a 24-hour TTL and a maximum of 500 keys. Identical resolves use single-flight; a changed request for the same customer, placement, and screen cancels the stale request.

Diagnostics #

getDiagnostics() returns aggregate counters and an immutable per-placement snapshot. Placement names are trimmed, the first 100 distinct placements are tracked independently, and further placements are aggregated under __other__.

final diagnostics = await engage.getDiagnostics();
final home = diagnostics.placements['home_top_banner'];

debugPrint(
  'calls=${home?.resolveCalls}, network=${home?.networkRequests}, '
  'average=${home?.averageDurationMs}ms',
);

Each DatraEngagePlacementDiagnostics contains resolve calls, real network requests, successful and empty responses, timeouts, errors, cancellations, single-flight hits, and average/last/maximum duration. A caller cancelling its own subscription does not cancel or count as cancellation of a shared network request.

Compatibility #

React Native SDK Flutter SDK
@datra/engage-sdk 0.3.2 datra_engage_sdk 0.1.2

The declared minimum remains Flutter 3.19 (Dart 3.3). CI compiles a separate consumer fixture on Flutter 3.19.6 without resolving this package's development dependencies.

Discounts, rewards, and loyalty points #

Datra Engage tells the app which offer to show and carries the action/offer metadata. The SDK does not calculate or apply a discount, validate promotion eligibility, reserve a reward product, or debit loyalty points. Those operations must be confirmed by the Datra backend/order calculation endpoint. Never grant a discount or spend points based only on client-side message data.

Development checks #

dart format --output=none --set-exit-if-changed .
flutter analyze
flutter test
dart doc
dart pub publish --dry-run

Publishing to pub.dev is a separate manual release step.

0
likes
150
points
72
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for resolving and tracking Datra Engage in-app messages.

Homepage
Repository

Topics

#engagement #in-app-messaging #analytics #marketing

License

MIT (license)

Dependencies

dio, flutter

More

Packages that depend on datra_engage_sdk