cloudx_flutter 3.9.0
cloudx_flutter: ^3.9.0 copied to clipboard
Complete Flutter SDK wrapper for CloudX Core with targeting APIs and full ad lifecycle callbacks
CloudX Flutter SDK #
A Flutter plugin for the CloudX Mobile Ads platform. Monetize your Flutter apps with banner, MREC, interstitial, and rewarded ads on Android and iOS.
Installation #
Requires Flutter 3.0.0+, Dart 2.17.1+, Android API 23+, and iOS 13.0+. Your app's iOS floor is whichever is higher, the plugin's or your Flutter version's: Flutter 3.47 ships an engine that requires iOS 15.0, which is why the apps in this repo set platform :ios, '15.0'.
Add this to your pubspec.yaml:
dependencies:
cloudx_flutter: ^3.9.0
Then run:
flutter pub get
Native SDK versions #
The plugin pins one native CloudX SDK per platform. Its own version follows the iOS CloudXCore line; the Android pin is listed alongside it in the changelog.
| Platform | Dependency | Version |
|---|---|---|
| Android | io.cloudx:sdk |
4.7.0 |
| iOS | CloudXCore |
3.9.0 |
Ad-network adapters are not bundled. Add the ones you need to your host app; each adapter is versioned as <network-sdk-version>.<adapter-revision> and moves independently of the core SDK. The versions below are the ones the CloudX docs publish for the pinned cores.
Android Setup #
The plugin pulls in io.cloudx:sdk, which requires compileSdk 36 and Kotlin 2.1 or newer in the host app. Minimum SDK is API 23. Current Flutter releases raise the floor further (Flutter 3.47 enforces Gradle 8.14, AGP 8.11.1 and Kotlin 2.2.20); the demo app in this repository builds with those versions.
Add ad network adapters to your app's android/app/build.gradle.kts:
dependencies {
implementation("io.cloudx:adapter-meta:6.22.0.0") // Meta Audience Network 6.22.0
implementation("io.cloudx:adapter-vungle:7.7.7.0") // Vungle SDK 7.7.7
implementation("io.cloudx:adapter-inmobi:11.4.0.1") // InMobi SDK 11.4.0
implementation("io.cloudx:adapter-mintegral:17.1.71.1") // Mintegral SDK 17.1.71
implementation("io.cloudx:adapter-unityads:4.19.0.1") // Unity Ads SDK 4.19.0
implementation("io.cloudx:adapter-magnite:1.0.0.1") // Magnite Ads SDK 1.0.0
implementation("io.cloudx:adapter-moloco:4.11.0.0") // Moloco SDK 4.11.0
implementation("io.cloudx:adapter-verve:3.9.0.1") // Verve HyBid SDK 3.9.0
implementation("io.cloudx:adapter-googlewaterfall:25.4.0.0") // Google Mobile Ads SDK 25.4.0
implementation("io.cloudx:adapter-digitalturbine:8.4.7.1") // Digital Turbine Marketplace SDK 8.4.7
implementation("io.cloudx:adapter-pangle:8.2.0.4.0") // Pangle SDK 8.2.0.4
implementation("io.cloudx:adapter-mobilefuse:1.12.0.0") // MobileFuse SDK 1.12.0
implementation("io.cloudx:adapter-taurusx:1.18.3.0") // TaurusX SDK 1.18.3
}
AdMob Estimated demand is available on Android only. Use exactly one Google adapter per app: adapter-googlewaterfall (Google Mobile Ads SDK, above) or adapter-admob (io.cloudx:adapter-admob:1.3.0.4, GMA Next-Gen SDK). They ship the same classes and cannot be co-installed. Either Google adapter needs your AdMob app ID in AndroidManifest.xml:
<application>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy" />
</application>
Mintegral, Verve, Pangle and TaurusX ship their network SDKs outside Maven Central. Add the repositories you need to the project-level android/build.gradle.kts:
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea") } // Mintegral
maven { url = uri("https://verve.jfrog.io/artifactory/verve-gradle-release") } // Verve
maven { url = uri("https://artifact.bytedance.com/repository/pangle") } // Pangle
maven { url = uri("https://artifact.taurusx.com/artifactory/taurusx-sdk/") } // TaurusX
}
}
iOS Setup #
The CloudXCore pod is included automatically through the plugin's podspec.
Add ad network adapter pods to your app's ios/Podfile inside the target 'Runner' do block:
target 'Runner' do
use_frameworks! :linkage => :static
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
# Adapters for ad networks (add as needed)
pod 'CloudXMetaAdapter', '~> 6.22.0.0' # Meta Audience Network 6.22.0
pod 'CloudXVungleAdapter', '~> 7.7.6.0' # Vungle SDK 7.7.6
pod 'CloudXInMobiAdapter', '~> 11.4.1.0' # InMobi SDK 11.4.1
pod 'CloudXMintegralAdapter', '~> 8.1.6.0' # Mintegral SDK 8.1.6
pod 'CloudXUnityAdsAdapter', '~> 4.19.0.0' # Unity Ads SDK 4.19.0
pod 'CloudXMagniteAdapterV2', '~> 1.0.0.1' # Magnite SDK 1.0.0
pod 'CloudXMobileFuseAdapter', '~> 1.11.0.1' # MobileFuse SDK 1.11.0
pod 'CloudXMolocoAdapter', '~> 4.8.0.0' # Moloco SDK 4.8.0
pod 'CloudXVerveAdapter', '~> 3.9.0.0' # Verve HyBid SDK 3.9.0
pod 'CloudXDigitalTurbineAdapter', '~> 8.4.8.0' # Digital Turbine Marketplace SDK 8.4.8
pod 'CloudXGoogleWaterfallAdapter', '~> 13.6.0.4' # Google Mobile Ads SDK 13.6.0
pod 'CloudXPangleAdapter', '~> 8.2.0.7.0' # Pangle SDK 8.2.0.7
pod 'CloudXTaurusXAdapter', '~> 1.18.2.0' # TaurusX SDK 1.18.2
end
CloudXGoogleWaterfallAdapter needs your AdMob app ID as GADApplicationIdentifier in ios/Runner/Info.plist, and every network benefits from the SKAdNetworkItems list the CloudX iOS integration guide publishes.
Then run cd ios && pod install.
Initialization #
Initialize CloudX before creating any ads:
import 'package:cloudx_flutter/cloudx.dart';
// Optional: Enable verbose logging (development only)
CloudX.setMinLogLevel(CloudXLogLevel.verbose);
// Initialize the SDK
final result = await CloudX.initialize(
appKey: 'YOUR_APP_KEY',
);
if (result.success) {
print('CloudX SDK initialized successfully');
} else {
print('Failed to initialize CloudX SDK: ${result.errorCode} ${result.message}');
}
Ad Integration #
Banner Ads (320x50) #
Banners use the programmatic overlay approach — they overlay your content at a fixed screen position and stay in place while content scrolls underneath.
const adUnitId = 'home_banner';
// Set up listener before creating ads
CloudX.setBannerListener(CloudXAdViewListener(
onAdLoaded: (ad) => print('Banner loaded from ${ad.networkName}'),
onAdLoadFailed: (adUnitId, error) => print('Failed: ${error.message}'),
onAdClicked: (ad) => print('Clicked'),
onAdRevenuePaid: (ad) => print('Revenue: ${ad.revenue}'), // optional
));
// Create and show
CloudX.createBanner(
adUnitId: adUnitId,
position: CloudXAdViewPosition.bottomCenter,
);
CloudX.showBanner(adUnitId: adUnitId);
// Hide when needed
CloudX.hideBanner(adUnitId: adUnitId);
// Always destroy when done (e.g., in dispose)
CloudX.destroyBanner(adUnitId: adUnitId);
Banner ads auto-refresh by default. To control refresh manually:
CloudX.stopBannerAutoRefresh(adUnitId: adUnitId);
CloudX.loadBanner(adUnitId: adUnitId); // Manually load a new ad
CloudX.startBannerAutoRefresh(adUnitId: adUnitId); // Re-enable auto-refresh
Optional placement and custom data for tracking:
CloudX.setBannerPlacement(adUnitId: adUnitId, placement: 'home_screen');
CloudX.setBannerCustomData(adUnitId: adUnitId, customData: 'level:5,coins:100');
Position is fixed when the banner is created. To move one, destroy it and create it again at the new position; that starts a new auction.
You can attach extra parameters to the auction. Set them before the load, and
before createBanner to reach the first auction:
CloudX.setBannerExtraParameter(
adUnitId: adUnitId,
key: 'requestId',
value: 'request-456',
);
A value stays on the ad until you set the same key again or clear it, so it
applies to later loads and to auto-refresh auctions, not only the next one. Pass
value: null to clear a key.
The same setter exists for every format: setMrecExtraParameter,
setInterstitialExtraParameter and setRewardedExtraParameter.
MREC Ads (300x250) #
MREC ads work identically to banners but with a larger size. Set up a separate listener with CloudX.setMrecListener(...).
const adUnitId = 'home_mrec';
CloudX.setMrecListener(CloudXAdViewListener(
onAdLoaded: (ad) => print('MREC loaded from ${ad.networkName}'),
onAdLoadFailed: (adUnitId, error) => print('Failed: ${error.message}'),
onAdClicked: (ad) => print('Clicked'),
onAdRevenuePaid: (ad) => print('Revenue: ${ad.revenue}'), // optional
));
CloudX.createMrec(
adUnitId: adUnitId,
position: CloudXAdViewPosition.centered,
);
CloudX.showMrec(adUnitId: adUnitId);
// Always destroy when done (e.g., in dispose)
CloudX.destroyMrec(adUnitId: adUnitId);
MREC ads also auto-refresh by default. Use the same refresh control methods as banner ads.
Widget-Based Ads (Inline) - not public #
The CloudXAdView widget embedded an ad in the Flutter widget tree so it scrolled with content. It is no longer exported from cloudx.dart and is not part of the public API. A widget ad accepts only the ad unit, the format and the auto-refresh flag, so placement, custom data and extra parameters cannot be set on one, and the setBanner... setters do not reach it either. Use the positioned createBanner / createMrec APIs above.
The code is still in the package and both native bridges still register the platform view, so the sections below describe what it does today. If you need inline ads, ask us and we will finish it.
Nothing below compiles from the public import alone. cloudx.dart does not
export the widget, so reaching it means importing the unexported file directly,
which is unsupported and can change or disappear in any release:
// Unsupported: not exported from package:cloudx_flutter/cloudx.dart
import 'package:cloudx_flutter/cloudx_ad_view.dart';
CloudXAdView(
adUnitId: 'feed_banner',
adFormat: CloudXAdFormat.banner, // or CloudXAdFormat.mrec
listener: CloudXAdViewListener(
onAdLoaded: (ad) => print('Loaded from ${ad.networkName}'),
onAdLoadFailed: (adUnitId, error) => print('Failed: ${error.message}'),
onAdClicked: (ad) => print('Clicked'),
onAdRevenuePaid: (ad) => print('Revenue: ${ad.revenue}'),
),
)
Key differences from programmatic overlay ads:
- Embeds inline in the widget tree (scrolls with content)
- Each widget has its own per-instance listener (not global)
- Auto-loads on creation
- Automatically destroyed when the widget is removed from the tree
Auto-refresh is enabled by default. Control it via the isAutoRefreshEnabled parameter:
CloudXAdView(
adUnitId: 'feed_banner',
adFormat: CloudXAdFormat.banner,
isAutoRefreshEnabled: false, // disable auto-refresh
)
Changing isAutoRefreshEnabled dynamically (e.g., via setState) will start or stop auto-refresh without recreating the ad view.
Interstitial Ads #
Full-screen ads shown at natural transition points.
Note: Interstitials don't need a create call. The loadInterstitial method auto-creates the instance.
const adUnitId = 'level_complete';
CloudX.setInterstitialListener(CloudXInterstitialListener(
onAdLoaded: (ad) => print('Interstitial loaded'),
onAdLoadFailed: (adUnitId, error) => print('Failed: ${error.message}'),
onAdDisplayed: (ad) => print('Displayed'),
onAdDisplayFailed: (ad, error) => print('Display failed: ${error.message}'),
onAdClicked: (ad) => print('Clicked'),
onAdHidden: (ad) {
print('Hidden');
// Reload for next use
CloudX.loadInterstitial(adUnitId: adUnitId);
},
onAdRevenuePaid: (ad) => print('Revenue: ${ad.revenue}'), // optional
));
// Load
CloudX.loadInterstitial(adUnitId: adUnitId);
// Check if ready before showing
final isReady = await CloudX.isInterstitialReady(adUnitId: adUnitId);
if (isReady) {
CloudX.showInterstitial(adUnitId: adUnitId);
// Or with optional placement and custom data for tracking
// CloudX.showInterstitial(adUnitId: adUnitId, placement: 'level_complete', customData: 'level:5');
}
// Always destroy when done (e.g., in dispose)
CloudX.destroyInterstitial(adUnitId: adUnitId);
Rewarded Ads #
Full-screen ads that grant users a reward upon completion.
Note: Like interstitials, rewarded ads don't need a create call. The loadRewarded method auto-creates the instance.
const adUnitId = 'rewarded_coins';
CloudX.setRewardedListener(CloudXRewardedListener(
onAdLoaded: (ad) => print('Rewarded loaded'),
onAdLoadFailed: (adUnitId, error) => print('Failed: ${error.message}'),
onAdDisplayed: (ad) => print('Displayed'),
onAdDisplayFailed: (ad, error) => print('Display failed: ${error.message}'),
onAdClicked: (ad) => print('Clicked'),
onAdHidden: (ad) {
print('Hidden');
// Reload for next use
CloudX.loadRewarded(adUnitId: adUnitId);
},
onAdReceivedReward: (ad, reward) {
print('Earned ${reward.amount} ${reward.label}');
// Grant the reward to the user
},
onAdRevenuePaid: (ad) => print('Revenue: ${ad.revenue}'), // optional
));
// Load
CloudX.loadRewarded(adUnitId: adUnitId);
// Check if ready before showing
final isReady = await CloudX.isRewardedReady(adUnitId: adUnitId);
if (isReady) {
CloudX.showRewarded(adUnitId: adUnitId);
// Or with optional placement and custom data for tracking
// CloudX.showRewarded(adUnitId: adUnitId, placement: 'bonus_coins', customData: 'level:5');
}
// Always destroy when done (e.g., in dispose)
CloudX.destroyRewarded(adUnitId: adUnitId);
Ad Information (CloudXAd) #
The CloudXAd object is passed to listener callbacks and contains information about the loaded/displayed ad:
| Property | Type | Description |
|---|---|---|
adFormat |
CloudXAdFormat |
Ad format. Not an enum: the constants are the formats this plugin can load (banner, mrec, interstitial, rewarded), and a format the native SDKs report without a constant here (for example native, app_open) arrives carrying its reported value. |
adUnitId |
String |
The ad unit ID |
networkName |
String |
Name of the winning ad network |
networkPlacement |
String? |
Network-specific placement ID |
placement |
String? |
Custom placement set via setPlacement() |
revenue |
double |
Impression-level revenue in USD |
onAdLoaded: (ad) {
print('Ad format: ${ad.adFormat}');
print('Network: ${ad.networkName}');
print('Revenue: ${ad.revenue}');
},
Error Handling #
All SDK errors are returned as CloudXError objects in listener callbacks:
| Property | Type | Description |
|---|---|---|
code |
int |
Error code (see categories below) |
codeName |
String? |
The SDK's own name for the code, such as NO_FILL |
message |
String |
Human-readable description |
Error Code Categories
| Range | Category | Common Codes |
|---|---|---|
| 0 | General | internalError |
| 100-199 | Network | networkError, networkTimeout, networkServerError, networkNoConnection |
| 200-299 | Initialization | notInitialized, sdkDisabled, noAdaptersFound, invalidAppKey |
| 300-399 | Ad Loading | noFill, invalidAdUnit, adsDisabled, loadFailed, loadNotAllowedWhileShowing, loadRejectedConcurrency (Android) / loadRejectedTooManyConcurrentLoads (iOS) |
| 400-499 | Display | adNotReady, adAlreadyShowing, adNotFound, displayHostUnavailable |
| 600-699 | Adapter | adapterNoFill, adapterTimeout, adapterLoadTimeout, adapterInitializationError |
See CloudXErrorCode for the full list of error codes.
Advanced Features #
Debug Logging #
// Enable verbose logging (call before initialize)
CloudX.setMinLogLevel(CloudXLogLevel.verbose);
// Only enable during development
if (kDebugMode) {
CloudX.setMinLogLevel(CloudXLogLevel.verbose);
}
Impression-Level Revenue Tracking #
Use the optional onAdRevenuePaid callback on any listener to receive impression-level revenue (ILR) data. The CloudXAd object contains the revenue value in USD and the winning network name.
onAdRevenuePaid: (ad) {
print('Revenue: ${ad.revenue} from ${ad.networkName}');
},
Works with all ad formats (banner, MREC, interstitial, rewarded) and widget-based ads.
Trusted Arbiter (TPA) #
Trusted Arbiter compares a loaded CloudX ad with bids from other platforms you mediate yourself and returns the platform to show. Load every candidate in parallel, build one CloudXArbiterBid per loaded ad, run the arbiter once they have all settled (loaded or failed), store the result, and show the stored winner at the placement without another network call. Never run the arbiter on the show path.
CloudXArbiterResult? nextWinner;
// Runs once both candidates have loaded, ahead of the placement.
Future<void> prepareWinner(CloudXAd cloudXAd, String adMobAdUnitId) async {
nextWinner = await CloudX.arbiter(CloudXArbiterConfiguration(bids: [
CloudXArbiterBid.cloudX(cloudXAd), // the CloudXAd from onAdLoaded, unchanged
CloudXArbiterBid.adMob(adUnitId: adMobAdUnitId),
]));
}
// Runs at the placement. No network call here.
Future<void> showInterstitial(String cloudXAdUnitId) async {
final winner = nextWinner?.platform;
nextWinner = null;
if (winner == CloudXArbiterPlatform.cloudX) {
CloudX.showInterstitial(adUnitId: cloudXAdUnitId);
} else if (winner == CloudXArbiterPlatform.adMob) {
// show your AdMob interstitial
} // CloudXArbiterPlatform.none: no winner, carry on without an ad
}
The native SDK owns the timeout and the fallback and always completes: a single bid wins without a service call, and on timeout, error or an unavailable service the highest locally comparable USD bid wins. Do not wrap the call in your own timer or compare revenue yourself. Bids are also available for LevelPlay (CloudXArbiterBid.levelPlay), PubMatic (.pubMatic), Google Ad Manager (.gam) and any other mediator (.custom).
AdMob and Ad Manager bids carry no price. CloudX prices them from the revenue you forward after each Google impression, so forwarding Google's paid events is a required part of the integration. With google_mobile_ads, onPaidEvent reports valueMicros on both platforms:
ad.onPaidEvent = (ad, valueMicros, precision, currencyCode) {
CloudX.reportRevenueData(CloudXRevenueData(
platform: CloudXRevenuePlatform.adMob, // .gam for Ad Manager
revenue: valueMicros / 1000000.0,
adFormat: 'interstitial',
currencyCode: currencyCode,
precision: precision == PrecisionType.precise
? CloudXRevenuePrecision.exact
: CloudXRevenuePrecision.estimated,
adUnitId: ad.adUnitId, // the id you passed to CloudXArbiterBid.adMob
));
};
Running google_mobile_ads and CloudXGoogleWaterfallAdapter in the same iOS app. Both pin Google-Mobile-Ads-SDK to an exact version, so they install together only when the two pins name the same one; check the adapter's podspec against the google_mobile_ads release you are on before adding either. The demo pairs google_mobile_ads 7.0.0 with CloudXGoogleWaterfallAdapter ~> 12.14.0.3, both on Google Mobile Ads 12.14.0. An app that takes its Google demand only through CloudX has no such constraint and should use the current adapter.
The demo app's TPA tab (cloudx_flutter_demo_app/lib/arbiter/) is a complete interstitial and rewarded cycle against AdMob that can be copied into an app: arbiter_fullscreen_controller.dart holds the format-agnostic flow, and the interstitial and rewarded controllers add only their format's SDK calls. Trusted Arbiter must be enabled for your app in the CloudX dashboard; until it is, the SDK still answers every call from its local fallback, in which an AdMob bid with no reported revenue history cannot win.
Test Mode #
Test mode is server-controlled via device whitelisting. This provides better security and control over which devices receive test ads.
To enable test mode:
- Initialize the SDK with logging enabled and check the console for your device advertising ID
- Copy the advertising ID and add it to your device whitelist on the CloudX server dashboard
- The SDK will automatically configure adapters for test mode and include the test flag in bid requests
Note: Test mode is determined by the server, so you don't need to change any code between development and production builds.
Privacy Compliance #
The CloudX SDK supports GDPR and CCPA privacy compliance by reading standard IAB privacy strings from platform storage (SharedPreferences on Android, NSUserDefaults on iOS). These values are typically set automatically by your Consent Management Platform (CMP) such as Google UMP, OneTrust, or Sourcepoint.
How It Works
The SDK automatically detects user location and reads consent signals:
- EU Users (GDPR): Checks TCF v2 consent for purposes 1-4 and vendor consent (CloudX Vendor ID: 1510)
- US Users (CCPA): Checks for sale/sharing opt-out signals
- Other Regions: No restrictions applied
When consent is denied or user opts out, the SDK removes PII from ad requests:
- Advertising ID is cleared
- Geo coordinates (lat/lon) are removed
- User key-values are not sent
- Hashed user ID is excluded
Supported Privacy Keys
| Key | Standard | Description |
|---|---|---|
IABGPP_HDR_GppString |
GPP | Global Privacy Platform string (modern) |
IABGPP_GppSID |
GPP | Section IDs (e.g., "2" for EU, "7" for US-National, "8" for US-CA) |
IABTCF_TCString |
TCF v2 | GDPR consent string (legacy) |
IABTCF_gdprApplies |
TCF v2 | Whether GDPR applies (1 = yes, 0 = no) |
IABUSPrivacy_String |
US Privacy | CCPA privacy string (legacy, e.g., "1YNN") |
Note: The SDK prioritizes GPP (modern standard) over legacy TCF/US Privacy strings when both are available.
User Targeting #
// Set hashed user ID for targeting
CloudX.setHashedUserId('hashed-user-id');
// Set custom user key-value pairs
CloudX.setUserKeyValue('age', '25');
CloudX.setUserKeyValue('interests', 'gaming');
// Set custom app key-value pairs
CloudX.setAppKeyValue('app_version', '1.2.0');
CloudX.setAppKeyValue('build_type', 'release');
// Clear all custom key-values
CloudX.clearAllKeyValues();
Support #
For support, contact support@cloudx.io