diff --git a/packages/google_adsense/CHANGELOG.md b/packages/google_adsense/CHANGELOG.md
index 91f12194eb37..57f202439cce 100644
--- a/packages/google_adsense/CHANGELOG.md
+++ b/packages/google_adsense/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.1.1
+
+* Adds `AdSenseCodeParameters` configuration object for `adSense.initialize`.
+* Adds a 100ms delay to `adBreak` and `showAdFn`, so Flutter tapevents have time
+ to settle before an H5 Ad takes over the screen.
+
## 0.1.0
* Adds H5 Games Ads API as `h5` library.
diff --git a/packages/google_adsense/README.md b/packages/google_adsense/README.md
index e34f60c90b9f..ccecc9285412 100644
--- a/packages/google_adsense/README.md
+++ b/packages/google_adsense/README.md
@@ -2,9 +2,11 @@
This package is only intended for use by web **games**.
-Please apply to the beta using [this form]( https://adsense.google.com/start/h5-beta/?src=flutter). Once approved, you may use the package.
+Please apply to the H5 Games Ads beta using [this form][h5-beta-form]. Once
+approved, you may use the package.
-Without approval, your code may not behave as expected, and your AdSense account may face policy issues.
+**Without approval, your code may not behave as expected, and your AdSense
+account may face policy issues.**
# google_adsense
@@ -13,8 +15,12 @@ Without approval, your code may not behave as expected, and your AdSense account
This package provides a way to initialize and use AdSense on your Flutter Web app.
It includes libraries for the following products:
-* [H5 Games Ads](https://adsense.google.com/start/h5-games-ads/) (beta)
-* (Experimental) [AdSense Ad Unit](https://support.google.com/adsense/answer/9183549) Widget
+* [H5 Games Ads](https://adsense.google.com/start/h5-games-ads/) (in beta, please
+ apply using [this form][h5-beta-form])
+* [AdSense Ad Unit](https://support.google.com/adsense/answer/9183549) Widget
+ (experimental and invitation-only, not accepting applications now)
+
+[h5-beta-form]: https://adsense.google.com/start/h5-beta/?src=flutter
## Documentation
diff --git a/packages/google_adsense/doc/initialization.md b/packages/google_adsense/doc/initialization.md
index 9851e8a2441f..8b1b777b3022 100644
--- a/packages/google_adsense/doc/initialization.md
+++ b/packages/google_adsense/doc/initialization.md
@@ -31,3 +31,23 @@ void main() async {
runApp(const MyApp());
}
```
+
+## Configure additional AdSense code parameters
+
+You can pass an `AdSenseCodeParameters` object to the `adSense.initialize` call
+to configure additional settings, like a custom channel ID, or for regulatory
+compliance.
+
+
+```dart
+await adSense.initialize(
+ '0123456789012345',
+ adSenseCodeParameters: AdSenseCodeParameters(
+ adbreakTest: 'on',
+ adFrequencyHint: '30s',
+ ),
+);
+```
+
+Check the Google AdSense Help for a complete list of
+[AdSense code parameter descriptions](https://support.google.com/adsense/answer/9955214#adsense_code_parameter_descriptions).
diff --git a/packages/google_adsense/example/integration_test/core_test.dart b/packages/google_adsense/example/integration_test/core_test.dart
index abc4fc18f6c3..4193e414c1b7 100644
--- a/packages/google_adsense/example/integration_test/core_test.dart
+++ b/packages/google_adsense/example/integration_test/core_test.dart
@@ -33,7 +33,6 @@ void main() async {
group('adSense.initialize', () {
testWidgets('adds AdSense script tag.', (WidgetTester _) async {
final web.HTMLElement target = web.HTMLDivElement();
- // Given
await adSense.initialize(testClient, jsLoaderTarget: target);
@@ -46,6 +45,39 @@ void main() async {
expect(injected.async, true);
});
+ testWidgets('sets AdSenseCodeParameters in script tag.',
+ (WidgetTester _) async {
+ final web.HTMLElement target = web.HTMLDivElement();
+
+ await adSense.initialize(testClient,
+ jsLoaderTarget: target,
+ adSenseCodeParameters: AdSenseCodeParameters(
+ adHost: 'test-adHost',
+ admobInterstitialSlot: 'test-admobInterstitialSlot',
+ admobRewardedSlot: 'test-admobRewardedSlot',
+ adChannel: 'test-adChannel',
+ adbreakTest: 'test-adbreakTest',
+ tagForChildDirectedTreatment: 'test-tagForChildDirectedTreatment',
+ tagForUnderAgeOfConsent: 'test-tagForUnderAgeOfConsent',
+ adFrequencyHint: 'test-adFrequencyHint',
+ ));
+
+ final web.HTMLScriptElement injected =
+ target.lastElementChild! as web.HTMLScriptElement;
+
+ expect(injected.dataset['adHost'], 'test-adHost');
+ expect(injected.dataset['admobInterstitialSlot'],
+ 'test-admobInterstitialSlot');
+ expect(injected.dataset['admobRewardedSlot'], 'test-admobRewardedSlot');
+ expect(injected.dataset['adChannel'], 'test-adChannel');
+ expect(injected.dataset['adbreakTest'], 'test-adbreakTest');
+ expect(injected.dataset['tagForChildDirectedTreatment'],
+ 'test-tagForChildDirectedTreatment');
+ expect(injected.dataset['tagForUnderAgeOfConsent'],
+ 'test-tagForUnderAgeOfConsent');
+ expect(injected.dataset['adFrequencyHint'], 'test-adFrequencyHint');
+ });
+
testWidgets('Skips initialization if script is already present.',
(WidgetTester _) async {
final web.HTMLScriptElement script = web.HTMLScriptElement()
diff --git a/packages/google_adsense/example/integration_test/h5_test.dart b/packages/google_adsense/example/integration_test/h5_test.dart
index 0ca816239a2e..28ab89b460fb 100644
--- a/packages/google_adsense/example/integration_test/h5_test.dart
+++ b/packages/google_adsense/example/integration_test/h5_test.dart
@@ -38,7 +38,8 @@ void main() {
// Pump frames so we can see what happened with adBreak
await tester.pump();
- await tester.pump();
+ // Wait for the async bits of adBreak
+ await tester.pump(const Duration(milliseconds: 250));
expect(lastAdBreakPlacement, isNotNull);
expect(lastAdBreakPlacement!.type?.toDart, 'reward');
@@ -70,7 +71,8 @@ void main() {
// Pump frames so we can see what happened with adBreak
await tester.pump();
- await tester.pump();
+ // Wait for the async bits of adBreak
+ await tester.pump(const Duration(milliseconds: 250));
expect(lastPlacementInfo, isNotNull);
expect(lastPlacementInfo!.breakName, 'ok-for-tests');
@@ -91,7 +93,8 @@ void main() {
// Pump frames so we can see what happened with adBreak
await tester.pump();
- await tester.pump();
+ // Wait for the async bits of adBreak
+ await tester.pump(const Duration(milliseconds: 250));
expect(lastAdBreakPlacement!.name!.toDart, 'APFlutter-my-test-break');
});
@@ -119,6 +122,7 @@ void main() {
// Pump frames so we can see what happened with adConfig
await tester.pump();
+ // adConfig doesn't have async bits
await tester.pump();
expect(lastAdConfigParameters, isNotNull);
diff --git a/packages/google_adsense/example/lib/h5.dart b/packages/google_adsense/example/lib/h5.dart
index 0ddbd6478cee..f40cfef2f99e 100644
--- a/packages/google_adsense/example/lib/h5.dart
+++ b/packages/google_adsense/example/lib/h5.dart
@@ -12,7 +12,15 @@ import 'package:google_adsense/h5.dart';
// #enddocregion import-h5
void main() async {
- await adSense.initialize('0123456789012345');
+ // #docregion initialize-with-code-parameters
+ await adSense.initialize(
+ '0123456789012345',
+ adSenseCodeParameters: AdSenseCodeParameters(
+ adbreakTest: 'on',
+ adFrequencyHint: '30s',
+ ),
+ );
+ // #enddocregion initialize-with-code-parameters
runApp(const MyApp());
}
diff --git a/packages/google_adsense/lib/src/adsense/ad_unit_params.dart b/packages/google_adsense/lib/src/adsense/ad_unit_params.dart
index d31a7c2c5fa7..f023f242681e 100644
--- a/packages/google_adsense/lib/src/adsense/ad_unit_params.dart
+++ b/packages/google_adsense/lib/src/adsense/ad_unit_params.dart
@@ -32,12 +32,12 @@ class AdUnitParams {
/// The ads inside a Multiplex ad unit are arranged in a grid. You can specify how many rows and columns you want to show within that grid
/// Sets the number of rows
/// Requires setting [AdUnitParams.MATCHED_CONTENT_UI_TYPE]
- static const String MATCHED_CONTENT_ROWS_NUM = 'macthedContentRowsNum';
+ static const String MATCHED_CONTENT_ROWS_NUM = 'matchedContentRowsNum';
/// The ads inside a Multiplex ad unit are arranged in a grid. You can specify how many rows and columns you want to show within that grid
/// Sets the number of columns
/// Requires setting [AdUnitParams.MATCHED_CONTENT_UI_TYPE]
- static const String MATCHED_CONTENT_COLUMNS_NUM = 'macthedContentColumnsNum';
+ static const String MATCHED_CONTENT_COLUMNS_NUM = 'matchedContentColumnsNum';
/// testing environment flag, defaults to kIsDebug
static const String AD_TEST = 'adtest';
diff --git a/packages/google_adsense/lib/src/adsense/adsense.dart b/packages/google_adsense/lib/src/adsense/adsense.dart
index 07781299c901..2429927cff21 100644
--- a/packages/google_adsense/lib/src/adsense/adsense.dart
+++ b/packages/google_adsense/lib/src/adsense/adsense.dart
@@ -2,6 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-export 'ad_unit_configuration.dart';
-export 'ad_unit_params.dart' hide AdStatus, AdUnitParams;
-export 'ad_unit_widget.dart';
+export 'ad_unit_configuration.dart' show AdUnitConfiguration;
+export 'ad_unit_params.dart' show AdFormat, AdLayout, MatchedContentUiType;
+export 'ad_unit_widget.dart' show AdUnitWidget;
diff --git a/packages/google_adsense/lib/src/core/adsense_code_parameters.dart b/packages/google_adsense/lib/src/core/adsense_code_parameters.dart
new file mode 100644
index 000000000000..3ab48bfbe25f
--- /dev/null
+++ b/packages/google_adsense/lib/src/core/adsense_code_parameters.dart
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Configuration for various settings for game ads.
+///
+/// These are set as `data`-attributes in the AdSense script tag.
+class AdSenseCodeParameters {
+ /// Builds an AdSense code parameters object.
+ ///
+ /// The following parameters are available:
+ ///
+ /// * [adHost]: If you share your revenue with a host platform, use this parameter
+ /// to specify the host platform.
+ /// * [admobInterstitialSlot]: If your game runs in a mobile app, use this parameter
+ /// to request interstitial ads.
+ /// * [admobRewardedSlot]: If your game runs in a mobile app, use this parameter
+ /// to request rewarded ads.
+ /// * [adChannel]: You may include a
+ /// [custom channel ID](https://support.google.com/adsense/answer/10078316)
+ /// for tracking the performance of your ads.
+ /// * [adbreakTest]: Set this parameter to `'on'` to enable testing mode. This
+ /// lets you test your placements using fake ads.
+ /// * [tagForChildDirectedTreatment]: Use this parameter if you want to tag your
+ /// ad requests for treatment as child directed. For more information, refer to:
+ /// [Tag a site or ad request for child-directed treatment](https://support.google.com/adsense/answer/3248194).
+ /// * [tagForUnderAgeOfConsent]: Use this parameter if you want to tag your
+ /// European Economic Area (EEA), Switzerland, and UK ad requests for restricted
+ /// data processing treatment. For more information, refer to:
+ /// [Tag an ad request for EEA and UK users under the age of consent (TFUA)](https://support.google.com/adsense/answer/9009582).
+ /// * [adFrequencyHint]: The minimum average time interval between ads expressed
+ /// in seconds. If this value is `'120s'` then ads will not be shown more
+ /// frequently than once every two minutes on average. Note that this is a hint
+ /// that could be ignored or overridden by a server control in future.
+ ///
+ /// For more information about these parameters, check
+ /// [AdSense code parameter descriptions](https://support.google.com/adsense/answer/9955214#adsense_code_parameter_descriptions).
+ AdSenseCodeParameters({
+ String? adHost,
+ String? admobInterstitialSlot,
+ String? admobRewardedSlot,
+ String? adChannel,
+ String? adbreakTest,
+ String? tagForChildDirectedTreatment,
+ String? tagForUnderAgeOfConsent,
+ String? adFrequencyHint,
+ }) : _adSenseCodeParameters = {
+ if (adHost != null) 'adHost': adHost,
+ if (admobInterstitialSlot != null)
+ 'admobInterstitialSlot': admobInterstitialSlot,
+ if (admobRewardedSlot != null) 'admobRewardedSlot': admobRewardedSlot,
+ if (adChannel != null) 'adChannel': adChannel,
+ if (adbreakTest != null) 'adbreakTest': adbreakTest,
+ if (tagForChildDirectedTreatment != null)
+ 'tagForChildDirectedTreatment': tagForChildDirectedTreatment,
+ if (tagForUnderAgeOfConsent != null)
+ 'tagForUnderAgeOfConsent': tagForUnderAgeOfConsent,
+ if (adFrequencyHint != null) 'adFrequencyHint': adFrequencyHint,
+ };
+
+ final Map _adSenseCodeParameters;
+
+ /// `Map` representation of this configuration object.
+ Map get toMap => _adSenseCodeParameters;
+}
diff --git a/packages/google_adsense/lib/src/core/google_adsense.dart b/packages/google_adsense/lib/src/core/google_adsense.dart
index 527ac38347e6..9f846e33c08e 100644
--- a/packages/google_adsense/lib/src/core/google_adsense.dart
+++ b/packages/google_adsense/lib/src/core/google_adsense.dart
@@ -6,8 +6,11 @@ import 'package:flutter/widgets.dart';
import 'package:web/web.dart' as web;
import '../utils/logging.dart';
+import 'adsense_code_parameters.dart';
import 'js_interop/js_loader.dart';
+export 'adsense_code_parameters.dart' show AdSenseCodeParameters;
+
/// The web implementation of the AdSense API.
class AdSense {
bool _isInitialized = false;
@@ -15,17 +18,22 @@ class AdSense {
/// The [Publisher ID](https://support.google.com/adsense/answer/2923881).
late String adClient;
+ /// The (optional)
+ /// [AdSense Code Parameters](https://support.google.com/adsense/answer/9955214#adsense_code_parameter_descriptions).
+ AdSenseCodeParameters? adSenseCodeParameters;
+
/// Initializes the AdSense SDK with your [adClient].
///
/// The [adClient] parameter is your AdSense [Publisher ID](https://support.google.com/adsense/answer/2923881).
///
+ /// The [adSenseCodeParameters] let you configure various settings for your
+ /// ads. All parameters are optional. See
+ /// [AdSense code parameter descriptions](https://support.google.com/adsense/answer/9955214#adsense_code_parameter_descriptions).
+ ///
/// Should be called ASAP, ideally in the `main` method.
- //
- // TODO(dit): Add the "optional AdSense code parameters", and render them
- // in the right location (the script tag for h5 + the ins for display ads).
- // See: https://support.google.com/adsense/answer/9955214?hl=en#adsense_code_parameter_descriptions
Future initialize(
String adClient, {
+ AdSenseCodeParameters? adSenseCodeParameters,
@visibleForTesting bool skipJsLoader = false,
@visibleForTesting web.HTMLElement? jsLoaderTarget,
}) async {
@@ -34,8 +42,13 @@ class AdSense {
return;
}
this.adClient = adClient;
+ this.adSenseCodeParameters = adSenseCodeParameters;
if (!skipJsLoader) {
- await loadJsSdk(adClient, jsLoaderTarget);
+ await loadJsSdk(
+ adClient,
+ target: jsLoaderTarget,
+ dataAttributes: adSenseCodeParameters?.toMap,
+ );
} else {
debugLog('initialize called with skipJsLoader. Skipping loadJsSdk.');
}
diff --git a/packages/google_adsense/lib/src/core/js_interop/js_loader.dart b/packages/google_adsense/lib/src/core/js_interop/js_loader.dart
index 6c124ef5a5f7..12b44eacaa0b 100644
--- a/packages/google_adsense/lib/src/core/js_interop/js_loader.dart
+++ b/packages/google_adsense/lib/src/core/js_interop/js_loader.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:js_interop';
+import 'dart:js_interop_unsafe' show JSObjectUnsafeUtilExtension;
import 'package:web/web.dart' as web;
import '../../utils/logging.dart';
@@ -17,7 +18,14 @@ const String _URL =
///
/// [target] can be used to specify a different injection target than
/// `window.document.head`, and is normally used for tests.
-Future loadJsSdk(String adClient, web.HTMLElement? target) async {
+///
+/// [dataAttributes] are used to configure the dataset `data-` attributes of the
+/// created script element.
+Future loadJsSdk(
+ String adClient, {
+ web.HTMLElement? target,
+ Map? dataAttributes,
+}) async {
if (_sdkAlreadyLoaded(adClient, target)) {
debugLog('adsbygoogle.js already injected. Skipping call to loadJsSdk.');
return;
@@ -46,9 +54,21 @@ Future loadJsSdk(String adClient, web.HTMLElement? target) async {
script.src = scriptUrl;
}
+ _applyDataAttributes(script, dataAttributes);
+
(target ?? web.document.head)!.appendChild(script);
}
+// Applies a map of [attributes] to the `dataset` of [element].
+void _applyDataAttributes(
+ web.HTMLElement element,
+ Map? attributes,
+) {
+ attributes?.forEach((String key, String value) {
+ element.dataset.setProperty(key.toJS, value.toJS);
+ });
+}
+
// Whether the script for [adClient] is already injected.
//
// [target] can be used to specify a different injection target than
diff --git a/packages/google_adsense/lib/src/h5/h5.dart b/packages/google_adsense/lib/src/h5/h5.dart
index 272ed387e23c..8b7e0a96842b 100644
--- a/packages/google_adsense/lib/src/h5/h5.dart
+++ b/packages/google_adsense/lib/src/h5/h5.dart
@@ -2,11 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
+
import '../core/js_interop/adsbygoogle.dart';
import 'h5_js_interop.dart';
-export 'enums.dart' hide MaybeEnum, interstitialBreakType;
-export 'h5_js_interop.dart' hide H5JsInteropExtension;
+export 'enums.dart'
+ show BreakFormat, BreakStatus, BreakType, PreloadAdBreaks, SoundEnabled;
+export 'h5_js_interop.dart'
+ show
+ AdBreakDonePlacementInfo,
+ AdBreakPlacement,
+ AdConfigParameters,
+ H5AdBreakDoneCallback,
+ H5AdDismissedCallback,
+ H5AdViewedCallback,
+ H5AfterAdCallback,
+ H5BeforeAdCallback,
+ H5BeforeRewardCallback,
+ H5OnReadyCallback,
+ H5ShowAdFn;
/// A client to request H5 Games Ads (Ad Placement API).
class H5GamesAdsClient {
@@ -16,7 +31,12 @@ class H5GamesAdsClient {
void adBreak(
AdBreakPlacement placementConfig,
) {
- adsbygoogle.adBreak(placementConfig);
+ // Delay the call to `adBreak` so tap users don't trigger a click on the ad
+ // on pointerup. This should leaves enough time for Flutter to settle its
+ // tap events, before triggering the H5 ad.
+ Timer(const Duration(milliseconds: 100), () {
+ adsbygoogle.adBreak(placementConfig);
+ });
}
/// Communicates the app's current configuration to the Ad Placement API.
diff --git a/packages/google_adsense/lib/src/h5/h5_js_interop.dart b/packages/google_adsense/lib/src/h5/h5_js_interop.dart
index 4b645a78188c..5a0c4342e8e4 100644
--- a/packages/google_adsense/lib/src/h5/h5_js_interop.dart
+++ b/packages/google_adsense/lib/src/h5/h5_js_interop.dart
@@ -2,6 +2,7 @@
// 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:js_interop';
import 'package:flutter/widgets.dart' show visibleForTesting;
@@ -117,9 +118,14 @@ extension type AdBreakPlacement._(JSObject _) implements JSObject {
beforeAd: beforeAd?.toJS,
afterAd: afterAd?.toJS,
beforeReward: beforeReward != null
- ? (JSFunction fn) {
+ ? (JSFunction showAdFn) {
beforeReward(() {
- fn.callAsFunction();
+ // Delay the call to `showAdFn` so tap users don't trigger a click on the
+ // ad on pointerup. This should leaves enough time for Flutter to settle
+ // its tap events, before triggering the H5 ad.
+ Timer(const Duration(milliseconds: 100), () {
+ showAdFn.callAsFunction();
+ });
});
}.toJS
: null,
diff --git a/packages/google_adsense/pubspec.yaml b/packages/google_adsense/pubspec.yaml
index efc767669770..26b865b5f4d9 100644
--- a/packages/google_adsense/pubspec.yaml
+++ b/packages/google_adsense/pubspec.yaml
@@ -2,7 +2,7 @@ name: google_adsense
description: A wrapper plugin with convenience APIs allowing easier inserting Google Adsense HTML snippets withing a Flutter UI Web application
repository: https://github.com/flutter/packages/tree/main/packages/google_adsense
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_adsense%22
-version: 0.1.0
+version: 0.1.1
environment:
sdk: ^3.4.0