Skip to main content

gantry 0.2.0 copy "gantry: ^0.2.0" to clipboard
gantry: ^0.2.0 copied to clipboard

Client SDK for Gantry. Fetch interstitial campaigns, submit leads and load content cards and pages in Flutter apps, with caching and retries built in.

gantry #

Client SDK for Gantry. It lets a Flutter app

  • pick the interstitial campaign to show when the app opens,
  • send a lead when the user asks to be contacted,
  • load content cards (announcements, campaigns, banners),
  • load static pages (FAQ, privacy notice) and
  • join a push bucket, so a topic push can be sent in parts,

with caching, retries, campaign priority and display frequency handled for you. The SDK gives you typed data; your app draws the UI.

Supports iOS and Android. It does not depend on Firebase.

Install #

dependencies:
  gantry: ^0.1.0

Android release builds need the internet permission in android/app/src/main/AndroidManifest.xml; Flutter adds it to debug builds only:

<uses-permission android:name="android.permission.INTERNET"/>

Set up #

Create one Gantry when the app starts and keep it for the app's lifetime.

import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:gantry/gantry.dart';

final gantry = Gantry(
  apiKey: 'gk_prod_...',   // the key of the Gantry environment this build uses
  appVersion: '2.3.1',     // your app's version: 1, 1.2 or 1.2.3
  remoteConfig: (key) => FirebaseRemoteConfig.instance.getString(key),
);

Each Gantry environment (dev, preprod, prod) has its own API key; put the matching key in each build. Create keys on the API keys screen in Gantry.

remoteConfig is how the SDK reads the two Remote Config parameters Gantry manages, bo_interstitial and bo_interstitial_targeted. Fetch and activate Remote Config before asking for an interstitial. If your app only uses cards and pages, leave remoteConfig out.

Tell the SDK who is signed in:

gantry.identify(customerId); // after sign-in, and on start if a session exists
gantry.reset();              // after sign-out

The customer id is the one your Gantry target lists use: 1 to 64 letters, digits or _ - . :.

Interstitials #

final interstitial = await gantry.interstitials.next();
if (interstitial != null) {
  await gantry.interstitials.markShown(interstitial); // it goes on screen now
  await showMyInterstitial(interstitial);             // your UI
}

next() never throws and takes at most about two seconds. It returns null when there is nothing to show.

Which campaign is returned

  1. If bo_interstitial_targeted is true and a customer is identified, the SDK asks Gantry for a campaign targeted at that customer.
  2. Otherwise, or if there is none, or the request fails or is slow, it uses the campaign published to everyone in bo_interstitial, provided it is enabled, inside its date range, meant for this platform and for this app version or older.
  3. A campaign that was already shown as often as its frequency allows is skipped. If that happens to the targeted campaign, the general one is considered.

Frequency

Value Meaning
once Once per device.
daily Once per calendar day in the device's time zone.
everyLaunch Once each time the app is launched.

The SDK counts a display only when you call markShown. Call it as the interstitial goes on screen, not after the user closes it: if the app is killed while the interstitial is open, a once campaign would otherwise show again. The record is kept per device, not per customer, and survives reset().

Buttons

Each button has a label and an action. The SDK describes the action and your app carries it out:

switch (interstitial.primaryAction) {
  case GantryRedirectAction(:final deeplink):
    openDeeplink(deeplink);
  case GantryWebPageAction(:final url):
    launchUrl(url);
  case GantryLeadAction():
    await gantry.leads.submit(campaignId: interstitial.id);
  case GantryDismissAction():
    break;
}

Every action may carry a trackingEvent name for your analytics. The SDK does not send analytics itself.

Leads #

try {
  await gantry.leads.submit(campaignId: interstitial.id);
  showThanks();
} on GantryNotIdentifiedException {
  askToSignIn();
} on GantryException {
  showTryAgain();
}

A lead tells Gantry that the identified customer wants to be contacted about a campaign. It carries no contact details. Sending the same lead twice is harmless. The SDK retries once on a connection problem, a server error or a rate limit, so the call can take up to about twelve seconds; show progress.

Only show a lead button while gantry.isIdentified is true.

Content cards #

final cards = await gantry.contents.list();                 // all cards
final deals = await gantry.contents.list(category: 'kampanya');

Cards come in the order set in Gantry and are already filtered for the platform and the current date. Cards are not personal and work without identify.

Pages #

final page = await gantry.pages.get('kvkk');   // null if it is not published
if (page != null) {
  render(page.body.html.resolve(languageCode));
}

A body is available as markdown and as sanitized html; render whichever suits your app.

Cards and pages are cached in memory for a minute. After that the SDK checks for changes, and if that check fails it returns the previous result.

Push buckets #

Gantry can send a topic push in parts, for example a tenth of the audience every ten minutes, so the users who tap it do not reach your backend all at once. For that each device joins a bucket. The SDK picks the device's bucket and names the topics; your app does the subscribing, because the SDK does not depend on Firebase.

import 'package:firebase_messaging/firebase_messaging.dart';

final gantry = Gantry(
  apiKey: 'gk_prod_...',
  appVersion: '2.3.1',
  subscribeToTopic: FirebaseMessaging.instance.subscribeToTopic,
);

await gantry.push.sync(); // on every start, once notifications are allowed

FirebaseMessaging.instance.onTokenRefresh
    .listen((_) => gantry.push.sync());

sync() subscribes the device to three topics, for example bo_bucket_37, bo_decile_3 and bo_bucketed. It never throws; a failed subscription is reported at GantryLogLevel.warning and tried again on the next sync().

  • The bucket is a random number from 0 to 99. It belongs to the device: it is kept in the store and survives reset().
  • Keep subscribing to your own topics (all, news, ...) as before. The SDK only manages the bo_ bucket topics.
  • A device that has not joined a bucket still receives a staged push: it gets it in the last step, together with every other such device.
  • Receiving and showing the notification stays in your app.

Staged sending is switched on per app in Gantry, once the version that calls sync() is live.

Texts in two languages #

Texts are LocalizedText values with a Turkish text and an optional English one:

card.title.resolve('en');      // English, or Turkish if no English was entered
card.title.resolve('en-US');   // locale tags work too
card.title.tr;

Errors #

Call When something goes wrong
interstitials.next() Returns null.
interstitials.markShown() Does nothing.
contents.list(), pages.get() Returns the previous result if there is one, otherwise throws. A rejected API key always throws.
leads.submit() Throws after one retry.

Everything thrown for a runtime problem is a GantryException:

Type Meaning
GantryNetworkException No connection, or the server did not answer in time.
GantryUnauthorizedException The API key is wrong or was revoked.
GantryRateLimitedException Too many requests in a short time.
GantryServerException The server failed or sent something unexpected.
GantryRequestException The server rejected the request; for a lead, the campaign id is unknown.
GantryNotIdentifiedException The call needs identify() first.

A malformed argument (customer id, app version, page key, category) throws an ArgumentError straight away; that is a bug to fix, not a condition to handle.

To see what the SDK is doing, pass onLog:

Gantry(
  // ...
  onLog: (event) => debugPrint(event.toString()),
);

Log events never contain the API key or a customer id. A rejected API key is reported at GantryLogLevel.error; watch for it, because next() hides the failure by design.

Options #

Option Default Use
platform detected from the device Override the detected platform, for example in tests.
baseUrl https://app.gantryhq.net A self-hosted or local Gantry.
httpClient a new http.Client Your own client, for example with certificate pinning.
store shared_preferences Where the record of shown interstitials and the push bucket are kept.
subscribeToTopic none How the SDK subscribes the device to its push bucket topics.

About the API key #

The key ships inside your app, so treat it as an identifier, not a secret. It can read published content and campaigns and write leads for one environment. You can have two active keys per environment: create a new one, ship it, and revoke the old one once old app versions have faded out.

Example #

example/ is a small app that makes every call in this README. See the comments at the top of example/lib/main.dart.

1
likes
160
points
204
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Client SDK for Gantry. Fetch interstitial campaigns, submit leads and load content cards and pages in Flutter apps, with caching and retries built in.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, http, meta, shared_preferences

More

Packages that depend on gantry