ads_core_flutter 0.1.0
ads_core_flutter: ^0.1.0 copied to clipboard
Ready-to-use Flutter ads toolkit for Android and iOS with banner, interstitial, rewarded, app open, and native template helpers.
import 'package:ads_core_flutter/ads_core_flutter.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const AdsExampleApp());
}
class AdsExampleApp extends StatelessWidget {
const AdsExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Ads Core Flutter Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFDF6D14)),
useMaterial3: true,
),
home: const AdsExampleHomePage(),
);
}
}
class AdsExampleHomePage extends StatefulWidget {
const AdsExampleHomePage({super.key});
@override
State<AdsExampleHomePage> createState() => _AdsExampleHomePageState();
}
class _AdsExampleHomePageState extends State<AdsExampleHomePage>
with WidgetsBindingObserver {
final AdsController _ads = AdsController(
unitIds: const AdsPlatformUnitIds.googleTest(),
);
late final NativeAdPool _smallNativePool = _ads.createNativeAdPool(
layout: AdsNativeLayout.small(),
);
late final NativeAdPool _mediumNativePool = _ads.createNativeAdPool(
layout: AdsNativeLayout.medium(),
);
bool _adsReady = false;
bool _isShowingAppOpen = false;
String _rewardText = 'Reward not collected yet.';
static const List<_RecipePreview> _recipes = [
_RecipePreview(
title: 'Shan Noodle Bowl',
subtitle: 'Tomato gravy, chicken, roasted peanuts',
minutes: '20 min',
),
_RecipePreview(
title: 'Tea Leaf Salad',
subtitle: 'Crunchy beans, cabbage, lime dressing',
minutes: '15 min',
),
_RecipePreview(
title: 'Mohinga',
subtitle: 'Fish broth, rice noodles, crispy fritters',
minutes: '35 min',
),
_RecipePreview(
title: 'Coconut Rice',
subtitle: 'Fragrant jasmine rice with toasted sesame',
minutes: '18 min',
),
_RecipePreview(
title: 'Spicy Tofu Curry',
subtitle: 'Rich curry sauce with basil and garlic',
minutes: '25 min',
),
_RecipePreview(
title: 'Palm Sugar Pancake',
subtitle: 'Soft center, caramel edges, sesame topping',
minutes: '22 min',
),
];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initializeAds();
}
Future<void> _initializeAds() async {
await _ads.initialize();
if (!mounted) {
return;
}
setState(() {
_adsReady = true;
});
_smallNativePool.preloadPositions(const [0]);
_mediumNativePool.preloadPositions(const [0]);
await _ads.preloadInterstitial();
await _ads.preloadRewarded();
await _ads.preloadAppOpen();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_showAppOpen();
}
}
Future<void> _showInterstitial() async {
final shown = await _ads.showInterstitialIfReady();
if (!shown && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Interstitial ad is still loading.')),
);
await _ads.preloadInterstitial();
}
}
Future<void> _showRewarded() async {
final shown = await _ads.showRewardedIfReady(
onRewarded: (reward) {
if (!mounted) {
return;
}
setState(() {
_rewardText = 'Reward earned: ${reward.amount} ${reward.type}';
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Reward earned: ${reward.amount} ${reward.type}'),
),
);
},
);
if (!shown && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Rewarded ad is still loading.')),
);
await _ads.preloadRewarded();
}
}
Future<void> _showAppOpen() async {
if (_isShowingAppOpen || !_adsReady) {
return;
}
_isShowingAppOpen = true;
final shown = await _ads.showAppOpenIfReady();
if (!shown) {
await _ads.preloadAppOpen();
}
_isShowingAppOpen = false;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_smallNativePool.dispose();
_mediumNativePool.dispose();
_ads.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('ads_core_flutter'),
backgroundColor: colorScheme.surface,
),
bottomNavigationBar: ColoredBox(
color: colorScheme.surface,
child: SafeArea(
top: false,
child: _adsReady
? AdsBanner(controller: _ads)
: const SizedBox.shrink(),
),
),
body: ListView(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
children: [
_HeroCard(
rewardText: _rewardText,
onShowInterstitial: _showInterstitial,
onShowRewarded: _showRewarded,
onShowAppOpen: _showAppOpen,
),
const SizedBox(height: 16),
Text('Native Layouts', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
if (_adsReady) ...[
const _SectionLabel(
title: 'Small template',
subtitle: 'Compact feed card with tighter height.',
),
AdsNativeFeedItem(pool: _smallNativePool, position: 0),
const _SectionLabel(
title: 'Medium template',
subtitle: 'Taller native template for rich media.',
),
AdsNativeFeedItem(pool: _mediumNativePool, position: 0),
],
const SizedBox(height: 16),
Text('Recipe Feed', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
for (int index = 0; index < _recipes.length; index++)
_RecipeCard(recipe: _recipes[index]),
],
),
);
}
}
class _SectionLabel extends StatelessWidget {
const _SectionLabel({required this.title, required this.subtitle});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(4, 8, 4, 2),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 2),
Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
],
),
);
}
}
class _HeroCard extends StatelessWidget {
const _HeroCard({
required this.rewardText,
required this.onShowInterstitial,
required this.onShowRewarded,
required this.onShowAppOpen,
});
final String rewardText;
final VoidCallback onShowInterstitial;
final VoidCallback onShowRewarded;
final VoidCallback onShowAppOpen;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colorScheme.primaryContainer,
colorScheme.surfaceContainerHighest,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ready-to-use mobile ads package.',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'This example uses Google test IDs. Replace them with production '
'Android and iOS unit IDs before release.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
FilledButton(
onPressed: onShowInterstitial,
child: const Text('Show interstitial'),
),
FilledButton.tonal(
onPressed: onShowRewarded,
child: const Text('Show rewarded'),
),
OutlinedButton(
onPressed: onShowAppOpen,
child: const Text('Show app open'),
),
],
),
const SizedBox(height: 16),
Text(rewardText, style: Theme.of(context).textTheme.bodyLarge),
],
),
);
}
}
class _RecipeCard extends StatelessWidget {
const _RecipeCard({required this.recipe});
final _RecipePreview recipe;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Card(
margin: const EdgeInsets.only(top: 12),
color: colorScheme.surfaceContainerLow,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(16),
),
alignment: Alignment.center,
child: const Icon(Icons.restaurant_menu_rounded),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
recipe.title,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
recipe.subtitle,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
const SizedBox(width: 12),
Chip(label: Text(recipe.minutes)),
],
),
),
);
}
}
class _RecipePreview {
const _RecipePreview({
required this.title,
required this.subtitle,
required this.minutes,
});
final String title;
final String subtitle;
final String minutes;
}