displayio_sdk 0.2.1 copy "displayio_sdk: ^0.2.1" to clipboard
displayio_sdk: ^0.2.1 copied to clipboard

Flutter plugin for the DIO (Display.io) ad SDKs — banner, infeed, interstitial, interscroller, audio and native ads on Android and iOS through one Dart API.

displayio_sdk #

The DIO (Display.io) Flutter plugin. It wraps the native DIO ad SDKs for Android and iOS, so a Flutter app loads and shows DIO ads through a single Dart API — every DIO ad format, one call site.

Status: all formats are implemented and device-verified on both platforms. The package is pre-1.0 — the API may still change.

Requirements #

Flutter >= 3.44.1 (Dart SDK ^3.12.1)
Android minSdk 24; pulls com.brandio.ads:sdk from https://maven.display.io/
iOS minimum deployment target 15.0; DIOSDK via Swift Package Manager

The Android artifact lives in DIO's own Maven repo, so add it to your app's settings.gradle.kts (dependencyResolutionManagement) or build.gradle.kts:

repositories {
    maven { url = uri("https://maven.display.io/") }
}

You need a DIO app id and placement ids from the DIO dashboard.

Install #

dependencies:
  displayio_sdk: ^0.2.1
import 'package:displayio_sdk/displayio_sdk.dart';

Quick start #

Initialize once at startup, then load a placement. The returned DioAd is a sealed type — switch on it to get the right presentation:

await DioSdk.instance.initialize(appId: 'APP_ID');

final ad = await DioSdk.instance.loadAd('PLACEMENT_ID');
switch (ad) {
  case DioInlineAd():       // banner / infeed / inline / interscroller
    DioAdView(ad: ad, reveal: true);
  case DioInterstitialAd():
    await ad.show();
  case DioAudioAd():        // InFlowAudio / InRing
    ad.play();
    ad.pause();
    ad.companionView;       // DioCompanionView? — null when absent
  case DioInGameAudioAd(): // the SDK's own square card, you position it
    Positioned(top: 16, right: 16, child: ad.cardView);
  case DioNativeAd():
    DioNativeAdView(ad: ad, child: /* your layout + slot widgets */);
}

The concrete ad type comes from ad.type, not from the placement type — an inline placement resolves at load time to a banner, infeed, or interscroller.

On iOS the first initialize call blocks the main thread briefly (the native SDK creates a WKWebView synchronously to read the user agent), which stalls Flutter rendering. Call it at startup / on a splash screen, and judge UI smoothness in profile or release builds, not debug.

Formats #

Format Presentation
Banner (HTML / video / audio) DioAdView
Infeed (video / display / audio) DioAdView
Interscroller (display / video / audio) DioAdView(reveal: true) — Dart-driven reveal parallax
Inline DioAdView (resolves to one of the above)
Interstitial (display / video / audio) ad.show()
InFlowAudio / InRing ad.play() / ad.pause() + optional companion view
In-Game Audio ad.cardView — the SDK's own card, positioned by you; or ad.play() / ad.pause() with the card off
Native DioNativeAdView + slot widgets

Native ads #

Native ads are publisher-rendered: the SDK gives you the text assets and fills its own media / icon / CTA slots; you build the layout with your own Flutter widgets.

DioNativeAdView(
  ad: ad,
  child: Column(
    children: [
      Text(ad.headline ?? ''),
      DioNativeIconView(ad: ad, size: 44),                // optional
      DioNativeMediaView(ad: ad, aspectRatio: 16 / 9),    // required — click + viewability root
      Text(ad.body ?? ''),
      DioNativeCtaView(ad: ad, text: ad.callToAction),    // optional, separately tracked CTA
    ],
  ),
);
  • DioNativeAdView registers and unregisters the slots for click and impression tracking; wrapping the layout in it is required.
  • Text (headline, body, callToAction, advertiser, price, privacy) is plain Flutter. There are no image URLs — icon and main image are rendered by the SDK into native slots, and click/viewability need real native views.
  • A Flutter-drawn CTA cannot be click-tracked; use DioNativeCtaView or rely on the media tap.

In-Game Audio #

An audio ad that the SDK renders as its own square card — gradient artwork, an animated equalizer, an AD badge, a progress ring and a native mute control. It takes no slot in the content flow: playback follows attachment and on-screen visibility, and the user mutes from the card. The card's look is yours to change, and it can be dropped entirely — see below.

You position the card yourself, which is the whole point of the format:

Stack(children: [
  gameSurface,
  Positioned(top: 16, right: 16, child: ad.cardView),
]);
ad.preferredSize;   // the card's natural (square) size — cardView uses it
await ad.dispose(); // or just remove the widget: detaching ends the ad

Resize it through the placement, not the widget:

await DioSdk.instance.loadAd('PID', options: const DioAdOptions(
  inGameAudio: DioInGameAudioConfig(customWidth: 160),  // the square's side
  audio: DioAudioControls(showSoundControl: true),      // iOS mute control
));

DioInGameAudioView(width:, height:) sizes the slot, not the creative — anything below preferredSize crops the progress ring and the mute control, which sit at the card's edges.

The mute control is asymmetric because the native SDKs are: on Android it is a view flag (DioInGameAudioView(showSoundControl: true), the default), on iOS a placement flag set before the request (DioAudioControls.showSoundControl). Showing it matters — with the control hidden, some creatives' decorative audio bars act as the ad's click target, so a tap meant to mute counts as a click.

If the creative has an end-card, the SDK swaps it into the same card; there is no separate companion widget. To keep the card on its own artwork, turn the companion off and the SDK does not even load it:

inGameAudio: DioInGameAudioConfig(companionEnabled: false),

Styling the card #

Every colour is optional — leave one out and the SDK keeps its own default.

await DioSdk.instance.loadAd('PID', options: const DioAdOptions(
  inGameAudio: DioInGameAudioConfig(
    backgroundTopLeft: Color(0xFF7B2FF7),
    backgroundBottomRight: Color(0xFFF107A3),
    accentColor: Color(0xFFFFE600),   // bars, now-playing glyph and progress ring
    badgeBackgroundColor: Color(0xFFFFE600),
    badgeTextColor: Color(0xFF000000),
    cornerRadius: 20,
    ringWidth: 4,
  ),
));

Individual elements can be switched off with showAdBadge, showNowPlayingGlyph and showProgressRing.

Your own image in the card #

Pass encoded PNG or JPEG bytes. The image fills the card's content inset by iconPadding, with the background showing through the gap, and replaces the default audio bars. It is decorative — it takes no touches, so the whole card stays tappable — and the campaign's companion still wins over it when there is one.

final bytes = (await rootBundle.load('assets/brand_mark.png'))
    .buffer.asUint8List();

await DioSdk.instance.loadAd('PID', options: DioAdOptions(
  inGameAudio: DioInGameAudioConfig(icon: bytes, iconPadding: 12),
));

Playing without a card #

Some games have nowhere to put a card but can still carry a sponsored audio track. Turning the card off keeps the audio and drops the UI entirely:

await DioSdk.instance.loadAd('PID', options: const DioAdOptions(
  inGameAudio: DioInGameAudioConfig(showCard: false),
));

await ad.play();    // nothing is heard until this
await ad.pause();

In this mode ad.hasCard is false and ad.cardView has nothing to render, so do not put it in the tree. The impression is counted when playback actually starts rather than on viewability, and the SDK ends the ad itself when the track finishes. play / pause also work with a card on screen, where the SDK otherwise drives playback from visibility.

Customization — DioAdOptions #

Per-load options span two axes: placement styling and the ORTB ad request. Every field is nullable and null means leave the native SDK default untouched.

final ad = await DioSdk.instance.loadAd('PID', options: DioAdOptions(
  // placement styling — only the group matching the resolved format applies
  audio: DioAudioControls(showSoundControl: false, accentColor: Color(0xFF00A2FF)),
  interscroller: DioInterscrollerConfig(headerText: 'Ad', showTapHint: false),
  infeed: DioInfeedConfig(fullWidth: true, ctaButtonInfeedColor: Colors.black),
  pureAudioAd: DioPureAudioAdConfig(autoRequestEnabled: false),
  inGameAudio: DioInGameAudioConfig(customWidth: 160),
  native: DioNativeRequestConfig(video: DioAssetParams(required: true)),
  // ORTB bid request / targeting — always applied
  request: DioAdRequestConfig(
    user: DioRequestUser(yob: 1990, gender: DioGender.female),
    bcat: ['IAB25'],
    tmax: 1000,
  ),
));

Ad metadata #

Each loaded ad carries a read-only snapshot of the bid response:

ad.metadata?.ecpm;
ad.metadata?.advertiserName;
ad.metadata?.creativeId;
ad.description; // "AdUnit: <type>, Placement id: <id>, Request id: <id>"

Placement listing (diagnostics) #

final placements = await DioSdk.instance.placements();
// [16209 (inGameAudio), 5133 (interstitial), 6363 (mediumrectangle), ...]

What the dashboard returned for the initialized app id — useful when a typo in the app id would otherwise only surface as a no-fill. It returns an empty list before initialize and on any native error, and never throws, so a diagnostic screen calling it from initState cannot break. DioPlacement.type is null for a unit this plugin does not model (the deprecated ones); rawType keeps the native string either way. It carries no ad state by design — the native SDK stays the single source of truth for that.

Server-to-server (ORTB) #

For a mediation / S2S flow, let the SDK build the bid request, POST it to your own exchange, and render the response you get back:

final requestJson = await DioSdk.instance.buildOrtbRequest('PID');
final token = await DioSdk.instance.token(); // DIO user token (user.buyeruid)

// ... POST requestJson to your exchange, receive ortbJson ...

final ad = await DioSdk.instance.loadAdFromOrtb('PID', ortbJson);

The result is an ordinary DioAd — the same widgets, show, events, metadata, and dispose apply. loadAdFromOrtb takes the raw ORTB JSON string; only the placement part of options applies (including native asset params, which the parser needs so asset ids match).

Known limitations #

  • iOS interscroller does not auto-pause/resume media on scroll visibility. That logic is scroll-driven inside the native SDK, and the plugin hosts the interscroller in a scroll view whose offset is pinned (the parallax is Dart-driven), so the SDK never sees a visibility change. Android is unaffected.
  • *TextSize options are Android-only — iOS folds text size into a font the SDK does not expose.
  • Custom fonts are not exposed. A Flutter-bundled font is invisible to native Typeface / UIFont without registering it in the native project, which would break the single-Dart-API promise.
  • interscroller / infeed styling applies only when the placement resolves directly to that format — reached through an inline placement, the concrete sub-placement is unknown at config time and the styling is skipped.
  • Audio output routing is the publisher's job on iOS (a global AVAudioSession); setAudioOutput is a no-op there. See example/ for a demo using the audio_session package.
  • Native ads use up to three platform views (media / icon / CTA) — mind the count in a scrolling feed, and note that viewability is measured on the media slot rather than the whole card.

Example #

example/ is a full test app covering every format, the customization and ORTB flows, and metadata:

cd example && flutter run

Placement inventory lives in example/lib/test_placements.dart and ad customization in example/lib/test_ad_config.dart.

License #

MIT — see LICENSE.

0
likes
160
points
208
downloads

Documentation

API reference

Publisher

verified publisherdisplay.io

Weekly Downloads

Flutter plugin for the DIO (Display.io) ad SDKs — banner, infeed, interstitial, interscroller, audio and native ads on Android and iOS through one Dart API.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on displayio_sdk

Packages that implement displayio_sdk