Flutter SDK
The adbnk_sdk Flutter plugin wraps the ADBNK native SDKs for Android and iOS. The native SDK on each platform does the rendering, click handling and tracking. The Dart API uses the same names as google_mobile_ads: AdRequest, AdLoadCallback, FullScreenContentCallback and so on.
Version: the version of the plugin package you received. AdbnkSdk.sdkVersion returns it.
Requirements
| Item | Requirement |
|---|---|
| Flutter | 3.0+ (Dart 2.19+) |
| Android | minSdk 21, compileSdk 34+ |
| iOS | 12.0+ |
You also need a zone ID for each placement. Create an App SDK zone in the publisher console, open Zones, then Get code.
Installation
The plugin and the native SDKs it depends on are delivered as an SDK package. To get it, contact your ADBNK account manager.
Unpack the plugin into your project (for example packages/adbnk_sdk) and add it as a path dependency:
# pubspec.yaml
dependencies:
adbnk_sdk:
path: packages/adbnk_sdkflutter pub getThe plugin also needs the native SDK files from your package:
- Android: put the AAR and POM at
adbnk_sdk/android/libs/repo/app/adbnk/adbnk-sdk/<version>/(file namesadbnk-sdk-<version>.aarand.pom). The plugin uses this folder as a local Maven repository. If yoursettings.gradlesetsdependencyResolutionManagementtoPREFER_SETTINGS, also addmaven { url = uri("<path>/adbnk_sdk/android/libs/repo") }there. - iOS: put
AdbnkSDK.xcframeworkatadbnk_sdk/ios/Frameworks/AdbnkSDK.xcframework, setplatform :ios, '12.0'or higher inios/Podfile, then runpod install.
If a native file is missing, the build stops and prints the path it expected.
Initialization
import 'package:adbnk_sdk/adbnk_sdk.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final status = await AdbnkSdk.initialize(
appId: 'com.example.myapp', // your app identifier
);
// status.ready, status.sdkVersion
runApp(const MyApp());
}Every load method throws a StateError if you call it before initialize has completed.
AdbnkConfig fields:
| Field | Default | Description |
|---|---|---|
testMode | false | See Test mode |
gdprConsent | null | GDPR consent. null means unknown and is treated as not consented. See Privacy |
coppaCompliance | false | Treat all requests as child-directed |
Other members: AdbnkSdk.isInitialized, AdbnkSdk.sdkVersion, setGDPRConsent(bool), setCOPPACompliance(bool), setTestMode(bool).
AdRequest
final request = AdRequest.builder()
.addKeyword('sports')
.setContentUrl('https://example.com/article/123')
.setNonPersonalizedAds(false)
.setTagForChildDirectedTreatment(null) // true / false / null = unspecified
.setTagForUnderAgeOfConsent(null)
.putExtra('placement', 'home_feed')
.build();const AdRequest.empty() is the default request.
Ad formats
| Format | Dart API |
|---|---|
| Banner | BannerAdWidget (widget) |
| Float banner (pinned top/bottom) | FloatBannerAd |
| Native | NativeAdWidget (widget) / NativeAd |
| Interstitial | InterstitialAd |
| Rewarded | RewardedAd |
| Rewarded interstitial | RewardedInterstitialAd |
| Splash | SplashAd |
| App open | AppOpenAd / AppOpenAdManager |
| Popup | PopupAd |
Full-screen formats: common pattern
Interstitial, rewarded, rewarded interstitial, splash, app open and popup ads all follow the same pattern:
static Future<void> load(
String zoneId, {
AdRequest? adRequest,
required AdLoadCallback<T> adLoadCallback,
});- In
onAdLoaded, setad.fullScreenContentCallback. - Call
ad.show(). - Call
ad.dispose()after the ad is dismissed.
FullScreenContentCallback<T>(
onAdShowedFullScreenContent: (ad) {},
onAdDismissedFullScreenContent: (ad) {},
onAdFailedToShowFullScreenContent: (ad, error) {},
onAdImpression: (ad) {},
onAdClicked: (ad) {},
)Each ad object can be shown once, and ad.isReady turns false afterwards.
One loaded ad per format
Each format holds only one loaded ad at a time, and events go to the most recently loaded instance. Show an interstitial before you load the next interstitial.
Interstitial
InterstitialAd? _interstitial;
void loadInterstitial() {
InterstitialAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<InterstitialAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<InterstitialAd>(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_interstitial = null;
loadInterstitial(); // preload the next one
},
onAdFailedToShowFullScreenContent: (ad, error) {
ad.dispose();
_interstitial = null;
},
);
_interstitial = ad;
},
onAdFailedToLoad: (error) => debugPrint('load failed: $error'),
),
);
}
void showInterstitial() {
if (_interstitial?.isReady ?? false) _interstitial!.show();
}Rewarded and rewarded interstitial
RewardedAd and RewardedInterstitialAd share the same API. Pass the reward callback to show(). It is called at most once per ad.
RewardedAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<RewardedAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<RewardedAd>(
onAdDismissedFullScreenContent: (ad) => ad.dispose(),
);
ad.show(onUserEarnedReward: (RewardItem reward) {
grantReward(reward.amount, reward.type);
});
},
onAdFailedToLoad: (error) {},
),
);You configure the reward amount and type on the zone in the publisher console.
Splash
SplashAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<SplashAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<SplashAd>(
onAdDismissedFullScreenContent: (ad) { ad.dispose(); goToHome(); },
onAdFailedToShowFullScreenContent: (ad, _) { ad.dispose(); goToHome(); },
);
ad.show();
},
onAdFailedToLoad: (_) => goToHome(),
),
);App open
AppOpenAd.load(...) works the same way as the other full-screen formats. To show an app open ad whenever the app returns to the foreground, use AppOpenAdManager and call it from your lifecycle observer:
class _AppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
AppOpenAdManager.instance.setup(zoneId: 'YOUR_ZONE_ID');
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
AppOpenAdManager.instance.showAdIfAvailable();
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
AppOpenAdManager.instance.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => const MaterialApp(home: HomePage());
}The manager preloads the next ad after each one is dismissed, and retries if a load fails.
Popup
PopupAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<PopupAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<PopupAd>(
onAdDismissedFullScreenContent: (ad) => ad.dispose(),
);
ad.show();
},
),
);Banner
BannerAdWidget is a platform view. It loads its ad when it is created.
BannerAdWidget(
zoneId: 'YOUR_ZONE_ID',
size: BannerSize.banner, // 320x50
adRequest: AdRequest.empty(), // optional
callback: BannerAdCallbackWrapper(
onLoaded: () {},
onFailed: (AdError e) {},
onImpression: () {},
onClicked: () {},
),
)BannerSize values:
| Value | Size |
|---|---|
banner | 320×50 |
largeBanner | 320×100 |
mediumRectangle | 300×250 |
fullBanner | 468×60 |
leaderboard | 728×90 |
adaptive | Screen width × 50 |
Choose the size that matches your zone. You can have several banners on screen at once.
Float banner
A float banner shows automatically as soon as it loads.
final floatBanner = FloatBannerAd(
zoneId: 'YOUR_ZONE_ID',
position: FloatBannerPosition.bottom,
height: 50,
margin: 0,
showCloseButton: true,
refreshInterval: 0, // seconds, 0 = no refresh
);
await floatBanner.load(callback: BannerAdCallbackWrapper(onFailed: (e) {}));
// floatBanner.hide(); floatBanner.show(); floatBanner.isShowing
// When you no longer need it:
floatBanner.dispose();Native
With NativeAdWidget, you supply the layout and the widget takes care of loading, recording the impression and handling taps:
NativeAdWidget(
zoneId: 'YOUR_ZONE_ID',
builder: (context, NativeAdData? ad, bool isLoaded) {
if (!isLoaded || ad == null) return const SizedBox.shrink();
return Card(
child: ListTile(
leading: ad.iconUrl != null ? Image.network(ad.iconUrl!) : null,
title: Text(ad.headline ?? ''),
subtitle: Text(ad.body ?? ''),
trailing: Text(ad.callToAction ?? ''),
),
);
},
)Tapping the widget opens the ad. Always show ad.sponsoredLabel (the "Ad" label) in your layout.
NativeAdData fields:
| Field | Description |
|---|---|
headline | Ad headline |
body | Ad body text |
callToAction | Call-to-action text |
iconUrl | Icon image URL |
imageUrl | Main image URL |
advertiser | Advertiser name |
rating | Rating |
price | Price |
store | Store |
sponsoredLabel | "Ad" label text |
For full control, use NativeAd directly:
- Call
await ad.load(callback: ...). - Read
ad.adData. - Call
ad.recordImpression()when the ad is actually shown to the user. - Call
ad.recordClick()only on a real user tap. - Call
ad.dispose()when you're done.
Click handling
You don't need to handle clicks yourself. The native SDK records the click, then opens the ad's deep link if it has one. If there's no deep link, or it can't be opened, it opens the landing page instead.
Privacy and compliance
The SDK never shows consent or permission dialogs. Your app collects consent and passes the result to the SDK.
GDPR (EEA/UK users). Collect consent with your CMP, then call:
await AdbnkSdk.setGDPRConsent(userConsented);Do this before loading ads. Until you set it, consent is unknown and the SDK treats it as not consented.
Non-personalized ads:
AdRequest.builder().setNonPersonalizedAds(true)COPPA / child-directed apps:
await AdbnkSdk.setCOPPACompliance(true); // whole app
AdRequest.builder().setTagForChildDirectedTreatment(true); // per request
AdRequest.builder().setTagForUnderAgeOfConsent(true);iOS ATT. The SDK doesn't show the ATT prompt. If you want the user to authorize tracking, do the following:
- Add
NSUserTrackingUsageDescriptiontoios/Runner/Info.plist. - Request authorization with an ATT plugin of your choice.
If the user doesn't authorize tracking, the SDK doesn't read the IDFA. Ads still serve normally. See iOS privacy.
Store disclosures. Google Play Data safety and App Store privacy labels follow the native SDKs. See the Android and iOS pages.
Test mode
testMode: true(orAdbnkSdk.setTestMode(true)) marks requests as test requests. ADBNK does not currently serve dedicated test creatives, so the ads you see during development are live. Don't repeatedly click ads on your own devices.- The SDK applies traffic-quality protection automatically. You don't need to configure anything.
Error codes
AdError has a code and a message.
Errors from the native SDK keep their platform codes:
- Android: codes 0–11. See Android error codes.
- iOS: codes 1001–1008. See iOS error codes.
Errors raised by the Dart layer itself:
| Code | Meaning |
|---|---|
| 1001 | Invalid zone ID |
| 1002 | No fill (also used when a native ad returns no assets) |
| 1003 | Network error |
| 1004 | Internal error (the message includes the native error code) |
| 1005 | Ad expired or not ready, for example show() was called before the ad loaded or after it was already shown |
| 1006 | Ad already shown |
| 1007 | Ad already loading |
| 1008 | Unsupported ad format |
| 1009 | Native plugin not available on this platform |
FAQ
Banners don't show on desktop or web. The plugin supports Android and iOS only. On other platforms, BannerAdWidget renders an empty box, no ad loads, and calls into the native SDK fail (error 1009 where an AdError is returned).
My callback fired for the wrong ad. Each full-screen format keeps only one loaded ad. Load the next ad only after the previous one has been shown or disposed.
Can I still use loadWithCallback and the *AdCallback classes? They still work, but they're deprecated. Use the static load(..., adLoadCallback:) together with FullScreenContentCallback instead.
Diagnostics. Full-screen ads expose ad.responseInfo?.responseId. Include this ID when you contact support.