Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[google_adsense] Add optional init parameters. #8297

Merged
merged 13 commits into from
Dec 16, 2024
6 changes: 6 additions & 0 deletions packages/google_adsense/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
14 changes: 10 additions & 4 deletions packages/google_adsense/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
20 changes: 20 additions & 0 deletions packages/google_adsense/doc/initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<?code-excerpt "../example/lib/h5.dart (initialize-with-code-parameters)"?>
```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).
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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()
Expand Down
10 changes: 7 additions & 3 deletions packages/google_adsense/example/integration_test/h5_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand All @@ -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');
});
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion packages/google_adsense/example/lib/h5.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down
4 changes: 2 additions & 2 deletions packages/google_adsense/lib/src/adsense/ad_unit_params.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<br>
/// Sets the number of rows<br>
/// 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<br>
/// Sets the number of columns<br>
/// 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';
Expand Down
6 changes: 3 additions & 3 deletions packages/google_adsense/lib/src/adsense/adsense.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -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 = <String, String>{
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<String, String> _adSenseCodeParameters;

/// `Map` representation of this configuration object.
Map<String, String> get toMap => _adSenseCodeParameters;
}
23 changes: 18 additions & 5 deletions packages/google_adsense/lib/src/core/google_adsense.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,34 @@ 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;

/// 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<void> initialize(
String adClient, {
AdSenseCodeParameters? adSenseCodeParameters,
@visibleForTesting bool skipJsLoader = false,
@visibleForTesting web.HTMLElement? jsLoaderTarget,
}) async {
Expand All @@ -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.');
}
Expand Down
22 changes: 21 additions & 1 deletion packages/google_adsense/lib/src/core/js_interop/js_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<void> loadJsSdk(String adClient, web.HTMLElement? target) async {
///
/// [dataAttributes] are used to configure the dataset `data-` attributes of the
/// created script element.
Future<void> loadJsSdk(
String adClient, {
web.HTMLElement? target,
Map<String, String>? dataAttributes,
}) async {
if (_sdkAlreadyLoaded(adClient, target)) {
debugLog('adsbygoogle.js already injected. Skipping call to loadJsSdk.');
return;
Expand Down Expand Up @@ -46,9 +54,21 @@ Future<void> 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<String, String>? 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
Expand Down
26 changes: 23 additions & 3 deletions packages/google_adsense/lib/src/h5/h5.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down
Loading
Loading