Skip to main content

datra_engage_sdk 0.1.1 copy "datra_engage_sdk: ^0.1.1" to clipboard
datra_engage_sdk: ^0.1.1 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.1 aligns resolve reliability with the React Native SDK and preserves a shared request when a single widget is cancelled.

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.1

Then import the package:

import 'package:datra_engage_sdk/datra_engage_sdk.dart';

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

Changing the customer cancels obsolete in-flight resolve requests.

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.

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