flutter_ads_orchestrator 0.1.0
flutter_ads_orchestrator: ^0.1.0 copied to clipboard
Smart AdMob orchestration for Flutter apps: preload, throttle, caps, placement rules, UMP consent. Supports interstitial and rewarded ads.
import 'package:flutter/material.dart';
import 'package:flutter_ads_orchestrator/flutter_ads_orchestrator.dart';
late final AdsOrchestrator orchestrator;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
orchestrator = AdsOrchestrator(
config: const AdsConfig(
// Always use test mode in this demo. **Never** ship release builds
// with `testMode: true` — Google bans accounts that serve test ads
// to real users.
testMode: true,
interstitial: InterstitialRules(
// In test mode these are auto-overridden with Google's test IDs.
adUnitIds: ['ca-app-pub-replace-with-yours/000000'],
minInterval: Duration(seconds: 30),
maxPerSession: 8,
maxPerDay: 30,
appOpenGuard: Duration(seconds: 5),
placements: {
'level_complete': PlacementRule.triggerEvery(3),
'pause_menu': PlacementRule.disabled(),
'game_over': PlacementRule.allowed(),
},
),
rewarded: RewardedRules(
adUnitIds: ['ca-app-pub-replace-with-yours/000000'],
minInterval: Duration(seconds: 15),
),
),
);
await orchestrator.initialize();
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter_ads_orchestrator demo',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
home: const MenuScreen(),
);
}
}
class MenuScreen extends StatelessWidget {
const MenuScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Demo menu')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => const GameScreen()),
),
child: const Text('Play (interstitial demo)'),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => const SettingsScreen()),
),
child: const Text('Settings (rewarded + privacy)'),
),
],
),
),
);
}
}
class GameScreen extends StatefulWidget {
const GameScreen({super.key});
@override
State<GameScreen> createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
int _level = 0;
String _lastResult = '—';
Future<void> _completeLevel() async {
setState(() => _level++);
final result = await orchestrator.showInterstitial(
placement: 'level_complete',
);
if (!mounted) return;
setState(() => _lastResult = _formatResult(result));
}
Future<void> _gameOver() async {
final result = await orchestrator.showInterstitial(placement: 'game_over');
if (!mounted) return;
setState(() => _lastResult = _formatResult(result));
}
Future<void> _pauseMenu() async {
// 'pause_menu' is configured as disabled — should always suppress.
final result = await orchestrator.showInterstitial(placement: 'pause_menu');
if (!mounted) return;
setState(() => _lastResult = _formatResult(result));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Game')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Level $_level', style: const TextStyle(fontSize: 32)),
const SizedBox(height: 24),
FilledButton(
onPressed: _completeLevel,
child: const Text('Complete level (triggerEvery 3)'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _gameOver,
child: const Text('Game over (allowed)'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _pauseMenu,
child: const Text('Pause menu (disabled — always suppressed)'),
),
const SizedBox(height: 24),
Text('Last result: $_lastResult'),
],
),
),
);
}
}
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
int _coins = 0;
String _lastResult = '—';
late final ValueNotifier<bool> _noAds;
@override
void initState() {
super.initState();
_noAds = ValueNotifier<bool>(orchestrator.noAdsFlag);
}
Future<void> _watchAdForCoins() async {
final result = await orchestrator.showRewarded(
placement: 'double_coins',
onUserEarnedReward: (reward) {
setState(() => _coins += reward.amount.toInt());
},
);
if (!mounted) return;
setState(() => _lastResult = _formatResult(result));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Coins: $_coins', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 24),
FilledButton(
onPressed: _watchAdForCoins,
child: const Text('Watch ad for coins (rewarded)'),
),
const SizedBox(height: 24),
ValueListenableBuilder<bool>(
valueListenable: _noAds,
builder: (context, value, _) => SwitchListTile(
title: const Text('No-ads (premium toggle)'),
value: value,
onChanged: (v) async {
await orchestrator.setNoAdsFlag(v);
_noAds.value = v;
},
),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: () => orchestrator.showPrivacyOptions(),
child: const Text('Show privacy options (UMP)'),
),
const SizedBox(height: 24),
Text('Last result: $_lastResult'),
],
),
),
);
}
}
String _formatResult(ShowResult result) {
return result.when(
shown: (id) => 'shown ($id)',
suppressed: (reason) => 'suppressed (${reason.name})',
failed: (err) => 'failed (${err.message})',
notReady: () => 'not ready',
);
}