1
0
mirror of https://github.com/flutter/samples.git synced 2026-04-02 09:43:05 +00:00

Add game_template (#1180)

Adds a template / sample for games built in Flutter, with all the bells and whistles, like ads, in-app purchases, audio, main menu, settings, and so on.

Co-authored-by: Parker Lougheed
Co-authored-by: Shams Zakhour
This commit is contained in:
Filip Hracek
2022-05-10 15:08:43 +02:00
committed by GitHub
parent 5143bcf302
commit daa024a829
208 changed files with 8993 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
// Copyright 2022, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'preloaded_banner_ad.dart';
/// Allows showing ads. A facade for `package:google_mobile_ads`.
class AdsController {
final MobileAds _instance;
PreloadedBannerAd? _preloadedAd;
/// Creates an [AdsController] that wraps around a [MobileAds] [instance].
///
/// Example usage:
///
/// var controller = AdsController(MobileAds.instance);
AdsController(MobileAds instance) : _instance = instance;
void dispose() {
_preloadedAd?.dispose();
}
/// Initializes the injected [MobileAds.instance].
Future<void> initialize() async {
await _instance.initialize();
}
/// Starts preloading an ad to be used later.
///
/// The work doesn't start immediately so that calling this doesn't have
/// adverse effects (jank) during start of a new screen.
void preloadAd() {
// TODO: When ready, change this to the Ad Unit IDs provided by AdMob.
// The current values are AdMob's sample IDs.
final adUnitId = defaultTargetPlatform == TargetPlatform.android
? 'ca-app-pub-3940256099942544/6300978111'
// iOS
: 'ca-app-pub-3940256099942544/2934735716';
_preloadedAd =
PreloadedBannerAd(size: AdSize.mediumRectangle, adUnitId: adUnitId);
// Wait a bit so that calling at start of a new screen doesn't have
// adverse effects on performance.
Future<void>.delayed(const Duration(seconds: 1)).then((_) {
return _preloadedAd!.load();
});
}
/// Allows caller to take ownership of a [PreloadedBannerAd].
///
/// If this method returns a non-null value, then the caller is responsible
/// for disposing of the loaded ad.
PreloadedBannerAd? takePreloadedAd() {
final ad = _preloadedAd;
_preloadedAd = null;
return ad;
}
}

View File

@@ -0,0 +1,205 @@
// Copyright 2022, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'ads_controller.dart';
import 'preloaded_banner_ad.dart';
/// Displays a banner ad that conforms to the widget's size in the layout,
/// and reloads the ad when the user changes orientation.
///
/// Do not use this widget on platforms that AdMob currently doesn't support.
/// For example:
///
/// ```dart
/// if (kIsWeb) {
/// return Text('No ads here! (Yet.)');
/// } else {
/// return MyBannerAd();
/// }
/// ```
///
/// This widget is adapted from pkg:google_mobile_ads's example code,
/// namely the `anchored_adaptive_example.dart` file:
/// https://github.com/googleads/googleads-mobile-flutter/blob/main/packages/google_mobile_ads/example/lib/anchored_adaptive_example.dart
class BannerAdWidget extends StatefulWidget {
const BannerAdWidget({Key? key}) : super(key: key);
@override
_BannerAdWidgetState createState() => _BannerAdWidgetState();
}
class _BannerAdWidgetState extends State<BannerAdWidget> {
static final _log = Logger('BannerAdWidget');
static const useAnchoredAdaptiveSize = false;
BannerAd? _bannerAd;
_LoadingState _adLoadingState = _LoadingState.initial;
late Orientation _currentOrientation;
@override
Widget build(BuildContext context) {
return OrientationBuilder(
builder: (context, orientation) {
if (_currentOrientation == orientation &&
_bannerAd != null &&
_adLoadingState == _LoadingState.loaded) {
_log.info(() => 'We have everything we need. Showing the ad '
'${_bannerAd.hashCode} now.');
return SizedBox(
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
);
}
// Reload the ad if the orientation changes.
if (_currentOrientation != orientation) {
_log.info('Orientation changed');
_currentOrientation = orientation;
_loadAd();
}
return const SizedBox();
},
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_currentOrientation = MediaQuery.of(context).orientation;
}
@override
void dispose() {
_log.info('disposing ad');
_bannerAd?.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
final adsController = context.read<AdsController>();
final ad = adsController.takePreloadedAd();
if (ad != null) {
_log.info("A preloaded banner was supplied. Using it.");
_showPreloadedAd(ad);
} else {
_loadAd();
}
}
/// Load (another) ad, disposing of the current ad if there is one.
Future<void> _loadAd() async {
if (!mounted) return;
_log.info('_loadAd() called.');
if (_adLoadingState == _LoadingState.loading ||
_adLoadingState == _LoadingState.disposing) {
_log.info('An ad is already being loaded or disposed. Aborting.');
return;
}
_adLoadingState = _LoadingState.disposing;
await _bannerAd?.dispose();
_log.fine('_bannerAd disposed');
setState(() {
_bannerAd = null;
_adLoadingState = _LoadingState.loading;
});
AdSize size;
if (useAnchoredAdaptiveSize) {
final AnchoredAdaptiveBannerAdSize? adaptiveSize =
await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
MediaQuery.of(context).size.width.truncate());
if (adaptiveSize == null) {
_log.warning('Unable to get height of anchored banner.');
size = AdSize.banner;
} else {
size = adaptiveSize;
}
} else {
size = AdSize.mediumRectangle;
}
assert(Platform.isAndroid || Platform.isIOS,
'AdMob currently does not support ${Platform.operatingSystem}');
_bannerAd = BannerAd(
// This is a test ad unit ID from
// https://developers.google.com/admob/android/test-ads. When ready,
// you replace this with your own, production ad unit ID,
// created in https://apps.admob.com/.
adUnitId: Theme.of(context).platform == TargetPlatform.android
? 'ca-app-pub-3940256099942544/6300978111'
: 'ca-app-pub-3940256099942544/2934735716',
size: size,
request: const AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) {
_log.info(() => 'Ad loaded: ${ad.responseInfo}');
setState(() {
// When the ad is loaded, get the ad size and use it to set
// the height of the ad container.
_bannerAd = ad as BannerAd;
_adLoadingState = _LoadingState.loaded;
});
},
onAdFailedToLoad: (ad, error) {
_log.warning('Banner failedToLoad: $error');
ad.dispose();
},
onAdImpression: (ad) {
_log.info('Ad impression registered');
},
onAdClicked: (ad) {
_log.info('Ad click registered');
},
),
);
return _bannerAd!.load();
}
Future<void> _showPreloadedAd(PreloadedBannerAd ad) async {
// It's possible that the banner is still loading (even though it started
// preloading at the start of the previous screen).
_adLoadingState = _LoadingState.loading;
try {
_bannerAd = await ad.ready;
} on LoadAdError catch (error) {
_log.severe('Error when loading preloaded banner: $error');
unawaited(_loadAd());
return;
}
if (!mounted) return;
setState(() {
_adLoadingState = _LoadingState.loaded;
});
}
}
enum _LoadingState {
/// The state before we even start loading anything.
initial,
/// The ad is being loaded at this point.
loading,
/// The previous ad is being disposed of. After that is done, the next
/// ad will be loaded.
disposing,
/// An ad has been loaded already.
loaded,
}

View File

@@ -0,0 +1,71 @@
// Copyright 2022, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:logging/logging.dart';
class PreloadedBannerAd {
static final _log = Logger('PreloadedBannerAd');
/// Something like [AdSize.mediumRectangle].
final AdSize size;
final AdRequest _adRequest;
BannerAd? _bannerAd;
final String adUnitId;
final _adCompleter = Completer<BannerAd>();
PreloadedBannerAd({
required this.size,
required this.adUnitId,
AdRequest? adRequest,
}) : _adRequest = adRequest ?? const AdRequest();
Future<BannerAd> get ready => _adCompleter.future;
Future<void> load() {
assert(Platform.isAndroid || Platform.isIOS,
'AdMob currently does not support ${Platform.operatingSystem}');
_bannerAd = BannerAd(
// This is a test ad unit ID from
// https://developers.google.com/admob/android/test-ads. When ready,
// you replace this with your own, production ad unit ID,
// created in https://apps.admob.com/.
adUnitId: adUnitId,
size: size,
request: _adRequest,
listener: BannerAdListener(
onAdLoaded: (ad) {
_log.info(() => 'Ad loaded: ${_bannerAd.hashCode}');
_adCompleter.complete(_bannerAd);
},
onAdFailedToLoad: (ad, error) {
_log.warning('Banner failedToLoad: $error');
_adCompleter.completeError(error);
ad.dispose();
},
onAdImpression: (ad) {
_log.info('Ad impression registered');
},
onAdClicked: (ad) {
_log.info('Ad click registered');
},
),
);
return _bannerAd!.load();
}
void dispose() {
_log.info('preloaded banner ad being disposed');
_bannerAd?.dispose();
}
}