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 small delay to the JS `showAdFn`, so Flutter events have time to settle
before the H5 Ad takes over the screen.

## 0.1.0

* Adds H5 Games Ads API as `h5` library.
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: 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
17 changes: 15 additions & 2 deletions packages/google_adsense/lib/src/h5/h5.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@
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 Down
7 changes: 6 additions & 1 deletion packages/google_adsense/lib/src/h5/h5_js_interop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -119,7 +120,11 @@ extension type AdBreakPlacement._(JSObject _) implements JSObject {
beforeReward: beforeReward != null
? (JSFunction fn) {
beforeReward(() {
fn.callAsFunction();
// Delay the call to `fn` so tap users don't trigger a click on
// the ad onTapUp.
Timer(const Duration(milliseconds: 100), () {
fn.callAsFunction();
ditman marked this conversation as resolved.
Show resolved Hide resolved
});
Copy link
Contributor

@sokoloff06 sokoloff06 Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ditman Things seem to work! This change is the only one Im not sure I completely understand, if there is someone else who can review this piece go for it, otherwise happy to jump on a 15 min call.
Approving anyways though not to be a blocker here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because in "tap" mode (on the phone), when you click on a Flutter button that results in rendering an ad, and that button ends up behind the actuable clickable area of the button, the ad "sees" that tap event, and immediately displays the ad (they might be listening to pointer up, instead of "click" to trigger the link of the ad).

I'm adding a small timer so there's enough "time" between flutter handling the tap event, and the ad actually rendering.

This might not be the best solution, but Flutter must not be the only one affected by this, I'll bring it up to the H5 people :)

});
}.toJS
: null,
Expand Down
2 changes: 1 addition & 1 deletion packages/google_adsense/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading