Flutter Integration Guide
Integrate AppSpike into your Flutter app and serve your first ad in under 10 minutes.
AppSpike sits on top of your existing ad networks and uses real-time bidding to route every impression to the highest-paying demand source. You keep your current ad network accounts — AppSpike optimizes across all of them.
Prerequisites
- Flutter 3.16 or later
- Dart 3.2 or later
- iOS 13+ / Android API 21+
- An AppSpike account and App ID (sign up)
Step 1: Add the SDK
Add the appspike_flutter package to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
appspike_flutter: ^1.0.0
Then run:
flutter pub get
Android Setup
Add the AppSpike Maven repository to your android/build.gradle:
android/build.gradleallprojects {
repositories {
google()
mavenCentral()
maven { url 'https://sdk.appspike.dev/android' }
}
}
Set your minimum SDK version in android/app/build.gradle:
android/app/build.gradleandroid {
defaultConfig {
minSdkVersion 21
}
}
iOS Setup
AppSpike's iOS dependencies are managed via CocoaPods. After adding the package, run:
cd ios
pod install
For App Tracking Transparency (ATT) compliance, add the NSUserTrackingUsageDescription key to your ios/Runner/Info.plist:
<key>NSUserTrackingUsageDescription</key>
<string>This allows us to show you more relevant ads.</string>
Step 2: Initialize the SDK
Initialize AppSpike as early as possible, ideally in your main() function before running the app:
import 'package:appspike_flutter/appspike_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final result = await AppSpike.initialize(appId: "YOUR_APP_ID");
if (result.isSuccess) {
debugPrint("AppSpike initialized — ${result.adapterCount} adapters loaded");
} else {
debugPrint("AppSpike init failed: ${result.error}");
}
runApp(const MyApp());
}
Step 3: Banner Ads
Use the AppSpikeBannerAd widget to embed a banner directly in your widget tree:
import 'package:flutter/material.dart';
import 'package:appspike_flutter/appspike_flutter.dart';
class BannerExample extends StatefulWidget {
const BannerExample({super.key});
@override
State<BannerExample> createState() => _BannerExampleState();
}
class _BannerExampleState extends State<BannerExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Banner Ad")),
body: Column(
children: [
// Your content
const Expanded(
child: Center(child: Text("App Content")),
),
// Banner pinned to bottom
AppSpikeBannerAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
size: BannerAdSize.banner,
onAdLoaded: () {
debugPrint("Banner loaded");
},
onAdFailedToLoad: (error) {
debugPrint("Banner failed: $error");
},
),
],
),
);
}
}
Available banner sizes: BannerAdSize.banner, BannerAdSize.largeBanner, BannerAdSize.mediumRectangle, BannerAdSize.fullBanner, BannerAdSize.leaderboard.
Step 4: Interstitial Ads
interstitial_example.dartimport 'package:flutter/material.dart';
import 'package:appspike_flutter/appspike_flutter.dart';
class InterstitialExample extends StatefulWidget {
const InterstitialExample({super.key});
@override
State<InterstitialExample> createState() => _InterstitialExampleState();
}
class _InterstitialExampleState extends State<InterstitialExample> {
InterstitialAd? _interstitialAd;
@override
void initState() {
super.initState();
_loadInterstitial();
}
Future<void> _loadInterstitial() async {
final result = await AppSpike.loadInterstitialAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
setState(() {
_interstitialAd = result.ad;
});
} else {
debugPrint("Interstitial failed to load: ${result.error}");
}
}
// Call this at a natural break point (e.g., between levels)
Future<void> _showInterstitial() async {
if (_interstitialAd == null) {
debugPrint("Interstitial not ready yet");
return;
}
await _interstitialAd!.show(
onAdDismissed: () {
_interstitialAd = null;
_loadInterstitial(); // Pre-load the next one
},
onAdFailedToShow: (error) {
debugPrint("Interstitial failed to show: $error");
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _showInterstitial,
child: const Text("Next Level"),
),
),
);
}
}
Step 5: Rewarded Ads
rewarded_example.dartimport 'package:flutter/material.dart';
import 'package:appspike_flutter/appspike_flutter.dart';
class RewardedExample extends StatefulWidget {
const RewardedExample({super.key});
@override
State<RewardedExample> createState() => _RewardedExampleState();
}
class _RewardedExampleState extends State<RewardedExample> {
RewardedAd? _rewardedAd;
@override
void initState() {
super.initState();
_loadRewarded();
}
Future<void> _loadRewarded() async {
final result = await AppSpike.loadRewardedVideoAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
setState(() {
_rewardedAd = result.ad;
});
} else {
debugPrint("Rewarded ad failed to load: ${result.error}");
}
}
// Call this when the user opts in (e.g., "Watch ad for 50 coins")
Future<void> _showRewarded() async {
if (_rewardedAd == null) {
debugPrint("Rewarded ad not ready yet");
return;
}
await _rewardedAd!.show(
onUserEarnedReward: (reward) {
debugPrint("User earned: ${reward.amount} ${reward.type}");
_grantReward(reward.amount);
},
onAdDismissed: () {
_rewardedAd = null;
_loadRewarded();
},
onAdFailedToShow: (error) {
debugPrint("Rewarded ad failed to show: $error");
},
);
}
void _grantReward(int amount) {
// Your reward logic here
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _rewardedAd != null ? _showRewarded : null,
child: const Text("Watch Ad for 50 Coins"),
),
),
);
}
}
Step 6: Native Ads
native_ad_example.dartimport 'package:flutter/material.dart';
import 'package:appspike_flutter/appspike_flutter.dart';
class NativeAdExample extends StatefulWidget {
const NativeAdExample({super.key});
@override
State<NativeAdExample> createState() => _NativeAdExampleState();
}
class _NativeAdExampleState extends State<NativeAdExample> {
NativeAd? _nativeAd;
@override
void initState() {
super.initState();
_loadNativeAd();
}
Future<void> _loadNativeAd() async {
final result = await NativeAd.load(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
setState(() {
_nativeAd = result.ad;
});
} else {
debugPrint("Native ad failed: ${result.error}");
}
}
@override
void dispose() {
_nativeAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_nativeAd == null) return const SizedBox.shrink();
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
_nativeAd!.iconWidget(width: 40, height: 40),
const SizedBox(width: 8),
Expanded(
child: Text(
_nativeAd!.headline,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 8),
Text(_nativeAd!.body),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => _nativeAd!.performClick(),
child: Text(_nativeAd!.callToAction),
),
],
),
),
);
}
}
Step 7: App Open Ads
App open ads display when users bring your app to the foreground. Use WidgetsBindingObserver to trigger them automatically:
import 'package:flutter/material.dart';
import 'package:appspike_flutter/appspike_flutter.dart';
class AppOpenAdManager with WidgetsBindingObserver {
AppOpenAd? _appOpenAd;
void init() {
WidgetsBinding.instance.addObserver(this);
_loadAppOpenAd();
}
Future<void> _loadAppOpenAd() async {
final result = await AppSpike.loadAppOpenAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
_appOpenAd = result.ad;
} else {
debugPrint("App open ad failed to load: ${result.error}");
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed && _appOpenAd != null) {
_appOpenAd!.show(
onAdDismissed: () {
_appOpenAd = null;
_loadAppOpenAd();
},
onAdFailedToShow: (error) {
debugPrint("App open ad failed to show: $error");
},
);
}
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
}
}
Initialize it in your root widget:
my_app.dartclass MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _appOpenAdManager = AppOpenAdManager();
@override
void initState() {
super.initState();
_appOpenAdManager.init();
}
@override
void dispose() {
_appOpenAdManager.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomeScreen(),
);
}
}
Step 8: Rewarded Interstitial Ads
Rewarded interstitials combine the full-screen experience of an interstitial with an opt-in reward. Unlike standard rewarded ads, they can be shown without the user explicitly choosing to watch:
rewarded_interstitial_example.dartimport 'package:flutter/material.dart';
import 'package:appspike_flutter/appspike_flutter.dart';
class RewardedInterstitialExample extends StatefulWidget {
const RewardedInterstitialExample({super.key});
@override
State<RewardedInterstitialExample> createState() =>
_RewardedInterstitialExampleState();
}
class _RewardedInterstitialExampleState
extends State<RewardedInterstitialExample> {
RewardedInterstitialAd? _rewardedInterstitialAd;
@override
void initState() {
super.initState();
_loadRewardedInterstitial();
}
Future<void> _loadRewardedInterstitial() async {
final result = await AppSpike.loadRewardedInterstitialAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
setState(() {
_rewardedInterstitialAd = result.ad;
});
} else {
debugPrint("Rewarded interstitial failed to load: ${result.error}");
}
}
Future<void> _showRewardedInterstitial() async {
if (_rewardedInterstitialAd == null) {
debugPrint("Rewarded interstitial not ready yet");
return;
}
await _rewardedInterstitialAd!.show(
onUserEarnedReward: (reward) {
debugPrint("User earned: ${reward.amount} ${reward.type}");
_grantReward(reward.amount);
},
onAdDismissed: () {
_rewardedInterstitialAd = null;
_loadRewardedInterstitial();
},
onAdFailedToShow: (error) {
debugPrint("Rewarded interstitial failed to show: $error");
},
);
}
void _grantReward(int amount) {
// Your reward logic here
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _showRewardedInterstitial,
child: const Text("Continue"),
),
),
);
}
}
Centralized Ad Service
For production apps, manage all ads from a centralized service. This example uses a singleton pattern:
ad_service.dartimport 'package:appspike_flutter/appspike_flutter.dart';
import 'package:flutter/foundation.dart';
class AdService {
AdService._();
static final AdService instance = AdService._();
InterstitialAd? _interstitialAd;
RewardedAd? _rewardedAd;
bool _isInitialized = false;
Future<void> initialize() async {
final result = await AppSpike.initialize(appId: "YOUR_APP_ID");
if (result.isSuccess) {
_isInitialized = true;
await Future.wait([
_loadInterstitial(),
_loadRewarded(),
]);
} else {
debugPrint("AppSpike init failed: ${result.error}");
}
}
// ── Interstitial ──
Future<void> _loadInterstitial() async {
final result = await AppSpike.loadInterstitialAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
_interstitialAd = result.ad;
}
}
Future<void> showInterstitial({VoidCallback? onComplete}) async {
if (_interstitialAd == null) {
onComplete?.call();
return;
}
await _interstitialAd!.show(
onAdDismissed: () {
_interstitialAd = null;
_loadInterstitial();
onComplete?.call();
},
onAdFailedToShow: (_) {
onComplete?.call();
},
);
}
// ── Rewarded ──
Future<void> _loadRewarded() async {
final result = await AppSpike.loadRewardedVideoAd(
placementId: "YOUR_APPSPIKE_PLACEMENT",
);
if (result.isSuccess) {
_rewardedAd = result.ad;
}
}
bool get isRewardedReady => _rewardedAd != null;
Future<void> showRewarded({
required void Function(int amount) onReward,
VoidCallback? onFail,
}) async {
if (_rewardedAd == null) {
onFail?.call();
return;
}
await _rewardedAd!.show(
onUserEarnedReward: (reward) => onReward(reward.amount),
onAdDismissed: () {
_rewardedAd = null;
_loadRewarded();
},
onAdFailedToShow: (_) => onFail?.call(),
);
}
}
Usage from any widget:
// Show interstitial between levels
await AdService.instance.showInterstitial(
onComplete: () {
Navigator.pushNamed(context, '/level2');
},
);
// Show rewarded ad for coins
if (AdService.instance.isRewardedReady) {
await AdService.instance.showRewarded(
onReward: (amount) => PlayerData.addCoins(amount),
onFail: () => debugPrint("Rewarded ad not available"),
);
}
Testing
Enable test mode during development to avoid serving live ads:
final result = await AppSpike.initialize(
appId: "YOUR_APP_ID",
testMode: true,
);
// Test ads will always fill and never charge
Use the built-in ad inspector to debug mediation and demand source selection:
AppSpike.openAdInspector();
The ad inspector shows:
- Which adapters are initialized
- Bid responses per demand source
- Fill rates and latency per network
- Which network won each impression
Best Practices
- Pre-load interstitials and rewarded ads as early as possible, then show them at natural break points. Never interrupt the user mid-action.
- Always reload after dismissal. Each ad object is single-use — load the next one in the
onAdDismissedcallback. - Ship with at least 3 ad network adapters. More demand sources means higher competition and higher eCPMs. AppSpike's mediation handles the rest.
- Use test mode during development. Live impressions from test devices waste ad budget and can flag your account.
- Don't gate core functionality behind rewarded ads. Use them for optional bonuses (extra lives, cosmetics, currency). Stores may reject apps that require ads to function.
- Dispose ad objects properly. Call
dispose()on native ads and banner controllers when your widget is removed from the tree to avoid memory leaks. - Handle platform differences. Test on both Android and iOS, as ad behavior and network availability can vary between platforms.