blob: 9e66a8718841caa9b0d92fc234eccb125b8c1e18 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. 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 'package:flutter/services.dart';
import 'package:meta/meta.dart';
/// [MobileAd] status changes reported to [MobileAdListener]s.
///
/// Applications can wait until an ad is [MobileAdEvent.loaded] before showing
/// it, to ensure that the ad is displayed promptly.
enum MobileAdEvent {
loaded,
failedToLoad,
clicked,
impression,
opened,
leftApplication,
closed,
}
/// The user's gender for the sake of ad targeting using [MobileAdTargetingInfo].
// Warning: the index values of the enums must match the values of the corresponding
// AdMob constants. For example MobileAdGender.female.index == kGADGenderFemale.
enum MobileAdGender {
unknown,
male,
female,
}
/// Signature for a [MobileAd] status change callback.
typedef void MobileAdListener(MobileAdEvent event);
/// Targeting info per the native AdMob API.
///
/// This class's properties mirror the native AdRequest API. See for example:
/// [AdRequest.Builder for Android](https://firebase.google.com/docs/reference/android/com/google/android/gms/ads/AdRequest.Builder).
class MobileAdTargetingInfo {
const MobileAdTargetingInfo({
this.keywords,
this.contentUrl,
this.birthday,
this.gender,
this.designedForFamilies,
this.childDirected,
this.testDevices,
this.requestAgent,
});
final List<String> keywords;
final String contentUrl;
final DateTime birthday;
final MobileAdGender gender;
final bool designedForFamilies;
final bool childDirected;
final List<String> testDevices;
final String requestAgent;
Map<String, dynamic> toJson() {
final Map<String, dynamic> json = <String, dynamic>{};
if (keywords != null && keywords.isNotEmpty) {
assert(keywords.every((String s) => s != null && s.isNotEmpty));
json['keywords'] = keywords;
}
if (contentUrl != null && contentUrl.isNotEmpty)
json['contentUrl'] = contentUrl;
if (birthday != null) json['birthday'] = birthday.millisecondsSinceEpoch;
if (gender != null) json['gender'] = gender.index;
if (designedForFamilies != null)
json['designedForFamilies'] = designedForFamilies;
if (childDirected != null) json['childDirected'] = childDirected;
if (testDevices != null && testDevices.isNotEmpty) {
assert(testDevices.every((String s) => s != null && s.isNotEmpty));
json['testDevices'] = testDevices;
}
if (requestAgent != null && requestAgent.isNotEmpty)
json['requestAgent'] = requestAgent;
return json;
}
}
/// A mobile [BannerAd] or [InterstitialAd] for the [FirebaseAdMobPlugin].
///
/// A [MobileAd] must be loaded with [load] before it is shown with [show].
///
/// A valid [unitId] is required.
abstract class MobileAd {
static final Map<int, MobileAd> _allAds = <int, MobileAd>{};
/// Default constructor, used by subclasses.
MobileAd({@required this.unitId, this.targetingInfo, this.listener}) {
assert(unitId != null && unitId.isNotEmpty);
assert(_allAds[id] == null);
_allAds[id] = this;
}
/// Optional targeting info per the native AdMob API.
final MobileAdTargetingInfo targetingInfo;
/// Identifies the source of ads for your application.
///
/// For testing use a [sample ad unit](https://developers.google.com/admob/ios/test-ads#sample_ad_units).
final String unitId;
/// Called when the status of the ad changes.
final MobileAdListener listener;
/// An internal id that identifies this mobile ad to the native AdMob plugin.
///
/// Plugin log messages will identify this property as the ad's `mobileAdId`.
int get id => hashCode;
MethodChannel get _channel => FirebaseAdMob.instance._channel;
/// Start loading this ad.
Future<bool> load();
/// Show this ad.
///
/// The ad must have been loaded with [load] first. If loading hasn't finished
/// the ad will not actually appear until the ad has finished loading.
///
/// The [listener] will be notified when the ad has finished loading or fails
/// to do so. An ad that fails to load will not be shown.
Future<bool> show() {
return _channel.invokeMethod("showAd", <String, dynamic>{'id': id});
}
/// Free the plugin resources associated with this ad.
///
/// Disposing a banner ad that's been shown removes it from the screen. Interstitial
/// ads can't be programatically removed from view.
Future<bool> dispose() {
assert(_allAds[id] != null);
_allAds[id] = null;
return _channel.invokeMethod("disposeAd", <String, dynamic>{'id': id});
}
Future<bool> _doLoad(String loadMethod) {
return _channel.invokeMethod(loadMethod, <String, dynamic>{
'id': id,
'unitId': unitId,
'targetingInfo': targetingInfo?.toJson(),
});
}
}
/// A banner ad for the [FirebaseAdMobPlugin].
class BannerAd extends MobileAd {
/// Create a BannerAd.
///
/// A valid [unitId] is required.
BannerAd({
@required String unitId,
MobileAdTargetingInfo targetingInfo,
MobileAdListener listener,
})
: super(unitId: unitId, targetingInfo: targetingInfo, listener: listener);
@override
Future<bool> load() => _doLoad("loadBannerAd");
}
/// A full-screen interstitial ad for the [FirebaseAdMobPlugin].
class InterstitialAd extends MobileAd {
/// Create an Interstitial.
///
/// A valid [unitId] is required.
InterstitialAd({
String unitId,
MobileAdTargetingInfo targetingInfo,
MobileAdListener listener,
})
: super(unitId: unitId, targetingInfo: targetingInfo, listener: listener);
@override
Future<bool> load() => _doLoad("loadInterstitialAd");
}
/// Support for Google AdMob mobile ads.
///
/// Before loading or showing an ad the plugin must be initialized with
/// an AdMob app id:
/// ```
/// FirebaseAdMob.instance.initialize(appId: myAppId);
/// ```
///
/// Apps can create, load, and show mobile ads. For example:
/// ```
/// BannerAd myBanner = new BannerAd(unitId: myBannerAdUnitId)
/// ..load()
/// ..show();
/// ```
///
/// See also:
///
/// * The example associated with this plugin.
/// * [BannerAd], a small rectangular ad displayed at the bottom of the screen.
/// * [InterstitialAd], a full screen ad that must be dismissed by the user.
class FirebaseAdMob {
@visibleForTesting
FirebaseAdMob.private(MethodChannel channel) : _channel = channel {
_channel.setMethodCallHandler(_handleMethod);
}
static final FirebaseAdMob _instance = new FirebaseAdMob.private(
const MethodChannel('plugins.flutter.io/firebase_admob'),
);
/// The single shared instance of this plugin.
static FirebaseAdMob get instance => _instance;
final MethodChannel _channel;
static const Map<String, MobileAdEvent> _methodToEvent =
const <String, MobileAdEvent>{
'onAdLoaded': MobileAdEvent.loaded,
'onAdFailedToLoad': MobileAdEvent.failedToLoad,
'onAdClicked': MobileAdEvent.clicked,
'onAdImpression': MobileAdEvent.impression,
'onAdOpened': MobileAdEvent.opened,
'onAdLeftApplication': MobileAdEvent.leftApplication,
'onAdClosed': MobileAdEvent.closed,
};
/// Initialize this plugin for the AdMob app specified by `appId`.
///
/// For testing one can use `ca-app-pub-3940256099942544~3347511713` for the `appId`.
Future<bool> initialize(
{@required String appId,
String trackingId,
bool analyticsEnabled: false}) {
assert(appId != null && appId.isNotEmpty);
assert(analyticsEnabled != null);
return _channel.invokeMethod("initialize", <String, dynamic>{
'appId': appId,
'trackingId': trackingId,
'analyticsEnabled': analyticsEnabled,
});
}
Future<dynamic> _handleMethod(MethodCall call) {
assert(call.arguments is Map);
final Map<String, dynamic> argumentsMap = call.arguments;
final int id = argumentsMap['id'];
if (id != null && MobileAd._allAds[id] != null) {
final MobileAd ad = MobileAd._allAds[id];
final MobileAdEvent event = _methodToEvent[call.method];
if (event != null && ad.listener != null) ad.listener(event);
}
return new Future<Null>(null);
}
}