diff --git a/packages/google_adsense/CHANGELOG.md b/packages/google_adsense/CHANGELOG.md index d0bd041d0ff6..a8f06137621f 100644 --- a/packages/google_adsense/CHANGELOG.md +++ b/packages/google_adsense/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.0.2 + +* **Breaking changes**: Reshuffles API exports: + * Makes `adSense.initialize` async. + * Removes the `adUnit` method, and instead exports the `AdUnitWidget` directly. + * Renames `experimental/google_adsense` to `experimental/ad_unit_widget.dart`. + * Removes the `AdStatus` and `AdUnitParams` exports. + * Removes the "stub" files, so this package is now web-only and must be used + through a conditional import. +* Tweaks several documentation pages to remove references to internal APIs. +* Splits tests to reflect the new code structure. + ## 0.0.1 * Initial release. diff --git a/packages/google_adsense/README.md b/packages/google_adsense/README.md index e7fb1b806938..c82209d480af 100644 --- a/packages/google_adsense/README.md +++ b/packages/google_adsense/README.md @@ -10,30 +10,42 @@ Please express interest joining Early Access program using [this form](https://d ## Usage ### Setup your AdSense account -1. [Make sure your site's pages are ready for AdSense](https://support.google.com/adsense/answer/7299563?hl=en&sjid=5790642343077592212-EU&visit_id=638657100661171978-1373860041&ref_topic=1319756&rd=1) -2. [Create your AdSense account](https://support.google.com/adsense/answer/10162?hl=en&sjid=5790642343077592212-EU&visit_id=638657100661171978-1373860041&ref_topic=1250103&rd=1) +1. [Make sure your site's pages are ready for AdSense](https://support.google.com/adsense/answer/7299563) +2. [Create your AdSense account](https://support.google.com/adsense/answer/10162) ### Initialize AdSense -To start displaying ads, initialize the AdSense with your [client/publisher ID](https://support.google.com/adsense/answer/105516?hl=en&sjid=5790642343077592212-EU) (only use numbers). +To start displaying ads, initialize AdSense with your [Publisher ID](https://support.google.com/adsense/answer/105516) (only use numbers). + ```dart -import 'package:google_adsense/experimental/google_adsense.dart'; +import 'package:google_adsense/experimental/ad_unit_widget.dart'; +import 'package:google_adsense/google_adsense.dart'; + +void main() async { + // Call `initialize` with your Publisher ID (pub-0123456789012345) + // (See: https://support.google.com/adsense/answer/105516) + await adSense.initialize('0123456789012345'); -void main() { - adSense.initialize( - '0123456789012345'); // TODO: Replace with your Publisher ID (pub-0123456789012345) - https://support.google.com/adsense/answer/105516?hl=en&sjid=5790642343077592212-EU runApp(const MyApp()); } - ``` -### Enable Auto Ads -In order to start displaying [Auto ads](https://support.google.com/adsense/answer/9261805?hl=en) make sure to configure this feature in your AdSense Console. If you want to display ad units within your app content, continue to the next step +### Displaying Auto Ads +In order to start displaying [Auto ads](https://support.google.com/adsense/answer/9261805): + +1. Configure this feature in your AdSense Console. -### Display ad unit Widget +Auto ads should start showing just with the call to `initialize`, when available. -1. Create [ad units](https://support.google.com/adsense/answer/9183549?hl=en&ref_topic=9183242&sjid=5790642343077592212-EU) in your AdSense account -2. Use relevant `AdUnitConfiguration` constructor as per table below +If you want to display ad units within your app content, continue to the next step + +### Display ad units (`AdUnitWidget`) + +To display an Ad unit in your Flutter application: + +1. Create [ad units](https://support.google.com/adsense/answer/9183549) in your AdSense account. + This will provide an HTML snippet, which you need to translate to Dart. +2. Pick the `AdUnitConfiguration` for your ad type: | Ad Unit Type | `AdUnitConfiguration` constructor method | |----------------|--------------------------------------------| @@ -42,12 +54,17 @@ In order to start displaying [Auto ads](https://support.google.com/adsense/answe | In-article Ads | `AdUnitConfiguration.inArticleAdUnit(...)` | | Multiplex Ads | `AdUnitConfiguration.multiplexAdUnit(...)` | -3. Translate data-attributes from snippet generated in AdSense Console into constructor arguments as described below: -- drop `data-` prefix -- translate kebab-case to camelCase -- no need to translate `data-ad-client` as it the value was already passed at initialization +3. The data-attributes from the generated snippet are available through the `AdUnitConfiguration` object. +Their Dart name is created as follows: + +- The `data-` prefix is **removed**. +- `kebab-case` becomes `camelCase` + +The only exception to this is `data-ad-client`, that is passed to `adSense.initialize`, +instead of through an `AdUnitConfiguration` object. + +For example snippet below: -For example snippet below ```html ``` -translates into +translates into: + ```dart -adSense.initialize( - '0123456789012345'); // TODO: Replace with your Publisher ID (pub-0123456789012345) - https://support.google.com/adsense/answer/105516?hl=en&sjid=5790642343077592212-EU +// Call `initialize` with your Publisher ID (pub-0123456789012345) +// (See: https://support.google.com/adsense/answer/105516) +await adSense.initialize('0123456789012345'); + ``` -and + +and: + ```dart - adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: '1234567890', // TODO: Replace with your Ad Unit ID - adFormat: AdFormat - .AUTO, // Remove AdFormat to make ads limited by height -)) + AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + // TODO: Replace with your Ad Unit ID + adSlot: '1234567890', + // Remove AdFormat to make ads limited by height + adFormat: AdFormat.AUTO, + ), +), ``` -#### Customize ad unit Widget +#### `AdUnitWidget` customizations + To [modify your responsive ad code](https://support.google.com/adsense/answer/9183363?hl=en&ref_topic=9183242&sjid=11551379421978541034-EU): 1. Make sure to follow [AdSense policies](https://support.google.com/adsense/answer/1346295?hl=en&sjid=18331098933308334645-EU&visit_id=638689380593964621-4184295127&ref_topic=1271508&rd=1) 2. Use Flutter instruments for [adaptive and responsive design](https://docs.flutter.dev/ui/adaptive-responsive) @@ -89,11 +115,14 @@ Container( constraints: const BoxConstraints(maxHeight: 100, maxWidth: 1200), padding: const EdgeInsets.only(bottom: 10), - child: adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: '1234567890', // TODO: Replace with your Ad Unit ID - adFormat: AdFormat - .AUTO, // Not using AdFormat to make ad unit respect height constraint - )), + child: AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + // TODO: Replace with your Ad Unit ID + adSlot: '1234567890', + // Do not use adFormat to make ad unit respect height constraint + // adFormat: AdFormat.AUTO, + ), + ), ), ``` ## Testing and common errors @@ -114,6 +143,7 @@ Make sure to set correct values to adSlot and adClient arguments ### Ad unfilled There is no deterministic way to make sure your ads are 100% filled even when testing. Some of the way to increase the fill rate: +- Ensure your ad units are correctly configured in AdSense - Try setting `adTest` parameter to `true` - Try setting AD_FORMAT to `auto` (default setting) - Try setting FULL_WIDTH_RESPONSIVE to `true` (default setting) diff --git a/packages/google_adsense/example/integration_test/test_js_interop.dart b/packages/google_adsense/example/integration_test/adsense_test_js_interop.dart similarity index 97% rename from packages/google_adsense/example/integration_test/test_js_interop.dart rename to packages/google_adsense/example/integration_test/adsense_test_js_interop.dart index d943ac980df9..aff8a4f4a55d 100644 --- a/packages/google_adsense/example/integration_test/test_js_interop.dart +++ b/packages/google_adsense/example/integration_test/adsense_test_js_interop.dart @@ -9,7 +9,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:ui'; -import 'package:google_adsense/experimental/google_adsense.dart'; +import 'package:google_adsense/src/adsense/ad_unit_params.dart'; import 'package:web/web.dart' as web; typedef VoidFn = void Function(); diff --git a/packages/google_adsense/example/integration_test/core_test.dart b/packages/google_adsense/example/integration_test/core_test.dart new file mode 100644 index 000000000000..e55f735e2714 --- /dev/null +++ b/packages/google_adsense/example/integration_test/core_test.dart @@ -0,0 +1,74 @@ +// 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. + +// TO run the test: +// 1. Run chrome driver with --port=4444 +// 2. Run the test from example folder with: flutter drive -d web-server --web-port 7357 --browser-name chrome --driver test_driver/integration_test.dart --target integration_test/ad_widget_test.dart + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_adsense/google_adsense.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:web/web.dart' as web; + +import 'adsense_test_js_interop.dart'; + +const String testClient = 'test_client'; +const String testScriptUrl = + 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-$testClient'; + +void main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late AdSense adSense; + + setUp(() async { + adSense = AdSense(); + }); + + tearDown(() { + clearAdsByGoogleMock(); + }); + + group('adSense.initialize', () { + testWidgets('adds AdSense script tag.', (WidgetTester _) async { + final web.HTMLElement target = web.HTMLDivElement(); + // Given + + await adSense.initialize(testClient, jsLoaderTarget: target); + + final web.HTMLScriptElement? injected = + target.lastElementChild as web.HTMLScriptElement?; + + expect(injected, isNotNull); + expect(injected!.src, testScriptUrl); + expect(injected.crossOrigin, 'anonymous'); + expect(injected.async, true); + }); + + testWidgets('Skips initialization if script is already present.', + (WidgetTester _) async { + final web.HTMLScriptElement script = web.HTMLScriptElement() + ..id = 'previously-injected' + ..src = testScriptUrl; + final web.HTMLElement target = web.HTMLDivElement()..appendChild(script); + + await adSense.initialize(testClient, jsLoaderTarget: target); + + expect(target.childElementCount, 1); + expect(target.firstElementChild?.id, 'previously-injected'); + }); + + testWidgets('Skips initialization if adsense object is already present.', + (WidgetTester _) async { + final web.HTMLElement target = web.HTMLDivElement(); + + // Write an empty noop object + mockAdsByGoogle(() {}); + + await adSense.initialize(testClient, jsLoaderTarget: target); + + expect(target.firstElementChild, isNull); + }); + }); +} diff --git a/packages/google_adsense/example/integration_test/ad_widget_test.dart b/packages/google_adsense/example/integration_test/experimental_ad_unit_widget_test.dart similarity index 64% rename from packages/google_adsense/example/integration_test/ad_widget_test.dart rename to packages/google_adsense/example/integration_test/experimental_ad_unit_widget_test.dart index 43b7dfb63041..12a1231b269a 100644 --- a/packages/google_adsense/example/integration_test/ad_widget_test.dart +++ b/packages/google_adsense/example/integration_test/experimental_ad_unit_widget_test.dart @@ -8,18 +8,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -// Ensure we don't use the singleton `adSense`, but the local copies to this plugin. -import 'package:google_adsense/experimental/google_adsense.dart' hide adSense; -import 'package:google_adsense/src/ad_unit_widget.dart'; +import 'package:google_adsense/experimental/ad_unit_widget.dart'; +// Ensure we don't use the `adSense` singleton for tests. +import 'package:google_adsense/google_adsense.dart' hide adSense; +import 'package:google_adsense/src/adsense/ad_unit_params.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:web/web.dart' as web; -import 'test_js_interop.dart'; +import 'adsense_test_js_interop.dart'; const String testClient = 'test_client'; const String testSlot = 'test_slot'; -const String testScriptUrl = - 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-$testClient'; void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -34,49 +32,7 @@ void main() async { clearAdsByGoogleMock(); }); - group('initialization', () { - testWidgets('Initialization adds AdSense snippet.', (WidgetTester _) async { - final web.HTMLElement target = web.HTMLDivElement(); - // Given - - adSense.initialize(testClient, jsLoaderTarget: target); - - final web.HTMLScriptElement? injected = - target.lastElementChild as web.HTMLScriptElement?; - - expect(injected, isNotNull); - expect(injected!.src, testScriptUrl); - expect(injected.crossOrigin, 'anonymous'); - expect(injected.async, true); - }); - - testWidgets('Skips initialization if script is already present.', - (WidgetTester _) async { - final web.HTMLScriptElement script = web.HTMLScriptElement() - ..id = 'previously-injected' - ..src = testScriptUrl; - final web.HTMLElement target = web.HTMLDivElement()..appendChild(script); - - adSense.initialize(testClient, jsLoaderTarget: target); - - expect(target.childElementCount, 1); - expect(target.firstElementChild?.id, 'previously-injected'); - }); - - testWidgets('Skips initialization if adsense object is already present.', - (WidgetTester _) async { - final web.HTMLElement target = web.HTMLDivElement(); - - // Write an empty noop object - mockAdsByGoogle(() {}); - - adSense.initialize(testClient, jsLoaderTarget: target); - - expect(target.firstElementChild, isNull); - }); - }); - - group('adWidget', () { + group('adSense.adUnit', () { testWidgets('Responsive (with adFormat) ad units reflow flutter', (WidgetTester tester) async { // The size of the ad that we're going to "inject" @@ -89,13 +45,14 @@ void main() async { ), ); - adSense.initialize(testClient); + await adSense.initialize(testClient); - final Widget adUnitWidget = adSense.adUnit( - AdUnitConfiguration.displayAdUnit( + final Widget adUnitWidget = AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( adSlot: testSlot, adFormat: AdFormat.AUTO, // Important! ), + adClient: adSense.adClient, ); await pumpAdWidget(adUnitWidget, tester); @@ -122,12 +79,13 @@ void main() async { ), ); - adSense.initialize(testClient); + await adSense.initialize(testClient); - final Widget adUnitWidget = adSense.adUnit( - AdUnitConfiguration.displayAdUnit( + final Widget adUnitWidget = AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( adSlot: testSlot, ), + adClient: adSense.adClient, ); final Widget constrainedAd = Container( @@ -151,11 +109,12 @@ void main() async { // When mockAdsByGoogle(mockAd(adStatus: AdStatus.UNFILLED)); - adSense.initialize(testClient); - final Widget adUnitWidget = adSense.adUnit( - AdUnitConfiguration.displayAdUnit( + await adSense.initialize(testClient); + final Widget adUnitWidget = AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( adSlot: testSlot, ), + adClient: adSense.adClient, ); await pumpAdWidget(adUnitWidget, tester); @@ -181,23 +140,32 @@ void main() async { ]), ); - adSense.initialize(testClient); + await adSense.initialize(testClient); final Widget bunchOfAds = Column( children: [ - adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: testSlot, - adFormat: AdFormat.AUTO, - )), - adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: testSlot, - adFormat: AdFormat.AUTO, - )), + AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + adSlot: testSlot, + adFormat: AdFormat.AUTO, + ), + adClient: adSense.adClient, + ), + AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + adSlot: testSlot, + adFormat: AdFormat.AUTO, + ), + adClient: adSense.adClient, + ), Container( constraints: const BoxConstraints(maxHeight: 100), - child: adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: testSlot, - )), + child: AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + adSlot: testSlot, + ), + adClient: adSense.adClient, + ), ), ], ); diff --git a/packages/google_adsense/example/integration_test/script_tag_test.dart b/packages/google_adsense/example/integration_test/script_tag_test.dart index c4671eaaf531..499712470f39 100644 --- a/packages/google_adsense/example/integration_test/script_tag_test.dart +++ b/packages/google_adsense/example/integration_test/script_tag_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; -import 'package:google_adsense/experimental/google_adsense.dart'; +import 'package:google_adsense/google_adsense.dart'; import 'package:integration_test/integration_test.dart'; import 'package:web/web.dart' as web; @@ -22,7 +22,7 @@ void main() async { 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-$testClient'; // When (using the singleton adSense from the plugin) - adSense.initialize(testClient); + await adSense.initialize(testClient); // Then final web.HTMLScriptElement? injected = diff --git a/packages/google_adsense/example/lib/main.dart b/packages/google_adsense/example/lib/main.dart index 91a4c3b2373d..88d6f83424ec 100644 --- a/packages/google_adsense/example/lib/main.dart +++ b/packages/google_adsense/example/lib/main.dart @@ -7,17 +7,20 @@ import 'package:flutter/material.dart'; // #docregion init -import 'package:google_adsense/experimental/google_adsense.dart'; +import 'package:google_adsense/experimental/ad_unit_widget.dart'; +import 'package:google_adsense/google_adsense.dart'; + +void main() async { +// #docregion init-min + // Call `initialize` with your Publisher ID (pub-0123456789012345) + // (See: https://support.google.com/adsense/answer/105516) + await adSense.initialize('0123456789012345'); -void main() { - // #docregion init-min - adSense.initialize( - '0123456789012345'); // TODO: Replace with your Publisher ID (pub-0123456789012345) - https://support.google.com/adsense/answer/105516?hl=en&sjid=5790642343077592212-EU // #enddocregion init-min runApp(const MyApp()); } - // #enddocregion init + /// The main app. class MyApp extends StatelessWidget { /// Constructs a [MyApp] @@ -66,13 +69,15 @@ class _MyHomePageState extends State { padding: const EdgeInsets.only(bottom: 10), child: // #docregion adUnit - adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: '1234567890', // TODO: Replace with your Ad Unit ID - adFormat: AdFormat - .AUTO, // Remove AdFormat to make ads limited by height - )) + AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + // TODO: Replace with your Ad Unit ID + adSlot: '1234567890', + // Remove AdFormat to make ads limited by height + adFormat: AdFormat.AUTO, + ), + ), // #enddocregion adUnit - , ), const Text( 'Responsive Ad Constrained by height of 100px and width of 1200px (to keep ad centered):', @@ -82,11 +87,14 @@ class _MyHomePageState extends State { constraints: const BoxConstraints(maxHeight: 100, maxWidth: 1200), padding: const EdgeInsets.only(bottom: 10), - child: adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: '1234567890', // TODO: Replace with your Ad Unit ID - adFormat: AdFormat - .AUTO, // Not using AdFormat to make ad unit respect height constraint - )), + child: AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + // TODO: Replace with your Ad Unit ID + adSlot: '1234567890', + // Do not use adFormat to make ad unit respect height constraint + // adFormat: AdFormat.AUTO, + ), + ), ), // #enddocregion constraints const Text( @@ -96,10 +104,13 @@ class _MyHomePageState extends State { height: 125, width: 125, padding: const EdgeInsets.only(bottom: 10), - child: adSense.adUnit(AdUnitConfiguration.displayAdUnit( - adSlot: '1234567890', // TODO: Replace with your Ad Unit ID - // adFormat: AdFormat.AUTO, // Not using AdFormat to make ad unit respect height constraint - isFullWidthResponsive: false)), + child: AdUnitWidget( + configuration: AdUnitConfiguration.displayAdUnit( + // TODO: Replace with your Ad Unit ID + adSlot: '1234567890', + isFullWidthResponsive: false, + ), + ), ), ], ), diff --git a/packages/google_adsense/lib/experimental/ad_unit_widget.dart b/packages/google_adsense/lib/experimental/ad_unit_widget.dart new file mode 100644 index 000000000000..9d165298d40d --- /dev/null +++ b/packages/google_adsense/lib/experimental/ad_unit_widget.dart @@ -0,0 +1,5 @@ +// 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. + +export '../src/adsense/adsense.dart'; diff --git a/packages/google_adsense/lib/experimental/google_adsense.dart b/packages/google_adsense/lib/experimental/google_adsense.dart deleted file mode 100644 index cbeabcc1418a..000000000000 --- a/packages/google_adsense/lib/experimental/google_adsense.dart +++ /dev/null @@ -1,7 +0,0 @@ -// 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. -export '../src/ad_unit_configuration.dart'; -export '../src/ad_unit_params.dart'; -export '../src/adsense_stub.dart' - if (dart.library.js_interop) '../src/adsense_web.dart'; diff --git a/packages/google_adsense/lib/google_adsense.dart b/packages/google_adsense/lib/google_adsense.dart index e7217c7d7b3f..ac0e7ae01c56 100644 --- a/packages/google_adsense/lib/google_adsense.dart +++ b/packages/google_adsense/lib/google_adsense.dart @@ -1,3 +1,5 @@ // 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. + +export 'src/core/google_adsense.dart'; diff --git a/packages/google_adsense/lib/src/ad_unit_configuration.dart b/packages/google_adsense/lib/src/ad_unit_configuration.dart deleted file mode 100644 index 94e34697358c..000000000000 --- a/packages/google_adsense/lib/src/ad_unit_configuration.dart +++ /dev/null @@ -1,112 +0,0 @@ -// 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. - -import 'package:flutter/foundation.dart'; - -import '../experimental/google_adsense.dart'; - -/// AdUnit configuration object. -/// -/// Arguments: -/// - `adSlot`: See [AdUnitParams.AD_SLOT] -/// - `adFormat`: See [AdUnitParams.AD_FORMAT] -/// - `adLayout`: See [AdUnitParams.AD_LAYOUT] -/// - `adLayoutKey`: See [AdUnitParams.AD_LAYOUT_KEY] -/// - `multiplexLayout`: See [AdUnitParams.MATCHED_CONTENT_UI_TYPE] -/// - `rowsNum`: See [AdUnitParams.MATCHED_CONTENT_ROWS_NUM] -/// - `columnsNum`: See [AdUnitParams.MATCHED_CONTENT_COLUMNS_NUM] -/// - `isFullWidthResponsive`: See [AdUnitParams.FULL_WIDTH_RESPONSIVE] -/// - `isAdTest`: See [AdUnitParams.AD_TEST] -class AdUnitConfiguration { - AdUnitConfiguration._internal({ - required String adSlot, - AdFormat? adFormat, - AdLayout? adLayout, - String? adLayoutKey, - MatchedContentUiType? matchedContentUiType, - int? rowsNum, - int? columnsNum, - bool? isFullWidthResponsive = true, - bool? isAdTest, - }) : _adUnitParams = { - AdUnitParams.AD_SLOT: adSlot, - if (adFormat != null) AdUnitParams.AD_FORMAT: adFormat.toString(), - if (adLayout != null) AdUnitParams.AD_LAYOUT: adLayout.toString(), - if (adLayoutKey != null) AdUnitParams.AD_LAYOUT_KEY: adLayoutKey, - if (isFullWidthResponsive != null) - AdUnitParams.FULL_WIDTH_RESPONSIVE: - isFullWidthResponsive.toString(), - if (matchedContentUiType != null) - AdUnitParams.MATCHED_CONTENT_UI_TYPE: - matchedContentUiType.toString(), - if (columnsNum != null) - AdUnitParams.MATCHED_CONTENT_COLUMNS_NUM: columnsNum.toString(), - if (rowsNum != null) - AdUnitParams.MATCHED_CONTENT_ROWS_NUM: rowsNum.toString(), - if (isAdTest != null && isAdTest) AdUnitParams.AD_TEST: 'on', - }; - - /// Creates In-article ad unit configuration object - AdUnitConfiguration.multiplexAdUnit({ - required String adSlot, - required AdFormat adFormat, - MatchedContentUiType? matchedContentUiType, - int? rowsNum, - int? columnsNum, - bool isFullWidthResponsive = true, - bool isAdTest = kDebugMode, - }) : this._internal( - adSlot: adSlot, - adFormat: adFormat, - matchedContentUiType: matchedContentUiType, - rowsNum: rowsNum, - columnsNum: columnsNum, - isFullWidthResponsive: isFullWidthResponsive, - isAdTest: isAdTest); - - /// Creates In-feed ad unit configuration object - AdUnitConfiguration.inFeedAdUnit({ - required String adSlot, - required String adLayoutKey, - AdFormat? adFormat, - bool isFullWidthResponsive = true, - bool isAdTest = kDebugMode, - }) : this._internal( - adSlot: adSlot, - adFormat: adFormat, - adLayoutKey: adLayoutKey, - isFullWidthResponsive: isFullWidthResponsive, - isAdTest: isAdTest); - - /// Creates In-article ad unit configuration object - AdUnitConfiguration.inArticleAdUnit({ - required String adSlot, - AdFormat? adFormat, - AdLayout adLayout = AdLayout.IN_ARTICLE, - bool isFullWidthResponsive = true, - bool isAdTest = kDebugMode, - }) : this._internal( - adSlot: adSlot, - adFormat: adFormat, - adLayout: adLayout, - isFullWidthResponsive: isFullWidthResponsive, - isAdTest: isAdTest); - - /// Creates Display ad unit configuration object - AdUnitConfiguration.displayAdUnit({ - required String adSlot, - AdFormat? adFormat, - bool isFullWidthResponsive = true, - bool isAdTest = kDebugMode, - }) : this._internal( - adSlot: adSlot, - adFormat: adFormat, - isFullWidthResponsive: isFullWidthResponsive, - isAdTest: isAdTest); - - Map _adUnitParams; - - /// Map containing all additional parameters of this configuration - Map get params => _adUnitParams; -} diff --git a/packages/google_adsense/lib/src/adsense/ad_unit_configuration.dart b/packages/google_adsense/lib/src/adsense/ad_unit_configuration.dart new file mode 100644 index 000000000000..740ce728a4a2 --- /dev/null +++ b/packages/google_adsense/lib/src/adsense/ad_unit_configuration.dart @@ -0,0 +1,185 @@ +// 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. + +import 'package:flutter/foundation.dart'; + +import 'ad_unit_params.dart'; + +/// Configuration to customize the [AdUnitWidget]. +/// +/// Contains named constructors for the following supported ad unit types: +/// +/// * Display (see: [AdUnitConfiguration.displayAdUnit]) +/// * In-feed (see: [AdUnitConfiguration.inFeedAdUnit]) +/// * In-article (see: [AdUnitConfiguration.inArticleAdUnit]) +/// * Multiplex (see: [AdUnitConfiguration.multiplexAdUnit]) +/// +/// Each constructor will use one or more of the following arguments: +/// +/// {@template pkg_google_adsense_parameter_adSlot} +/// * [adSlot]: Identifies a specific ad unit from the AdSense console. +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_adFormat} +/// * [adFormat]: (Desktop only) Specifies a general shape (horizontal, vertical, +/// and/or rectangle) that this ad unit should conform to. To learn more, check: +/// [How to use responsive ad tag parameters](https://support.google.com/adsense/answer/9183460). +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_adLayout} +/// * [adLayout]: Customizes the layout of this ad unit. See: +/// [Customize your in-feed ad](https://support.google.com/adsense/answer/9189957). +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_adLayoutKey} +/// * [adLayoutKey]: The key identifying the layout for this ad unit. +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_matchedContentUiType} +/// * [matchedContentUiType]: Controls the arrangement of the text and images in +/// this Multiplex ad unit. For example, you can choose to have the image and +/// text side by side, the image above the text, etc. More information: +/// [How to customize your responsive Multiplex ad unit](https://support.google.com/adsense/answer/7533385) +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_rowsNum} +/// * [rowsNum]: Specifies how many rows to show within the Multiplex ad unit grid. +/// Requires [matchedContentUiType] to be set. +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_columnsNum} +/// * [columnsNum]: Specifies how many columns to show within the Multiplex ad unit grid. +/// Requires [matchedContentUiType] to be set. +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_isFullWidthResponsive} +/// * [isFullWidthResponsive]: Determines whether this responsive ad unit expands +/// to use the full width of a visitor's mobile device screen. See: +/// [How to use responsive ad tag parameters](https://support.google.com/adsense/answer/9183460). +/// {@endtemplate} +/// {@template pkg_google_adsense_parameter_isAdTest} +/// * [isAdTest]: Whether this ad will be shown in a test environment. Defaults to `true` in debug mode. +/// {@endtemplate} +/// +/// For more information about ad units, check the +/// [Ad formats FAQ](https://support.google.com/adsense/answer/10734935) +/// in the Google AdSense Help. +class AdUnitConfiguration { + AdUnitConfiguration._internal({ + required String adSlot, + AdFormat? adFormat, + AdLayout? adLayout, + String? adLayoutKey, + MatchedContentUiType? matchedContentUiType, + int? rowsNum, + int? columnsNum, + bool? isFullWidthResponsive = true, + bool? isAdTest, + }) : _adUnitParams = { + AdUnitParams.AD_SLOT: adSlot, + if (adFormat != null) AdUnitParams.AD_FORMAT: adFormat.toString(), + if (adLayout != null) AdUnitParams.AD_LAYOUT: adLayout.toString(), + if (adLayoutKey != null) AdUnitParams.AD_LAYOUT_KEY: adLayoutKey, + if (isFullWidthResponsive != null) + AdUnitParams.FULL_WIDTH_RESPONSIVE: + isFullWidthResponsive.toString(), + if (matchedContentUiType != null) + AdUnitParams.MATCHED_CONTENT_UI_TYPE: + matchedContentUiType.toString(), + if (columnsNum != null) + AdUnitParams.MATCHED_CONTENT_COLUMNS_NUM: columnsNum.toString(), + if (rowsNum != null) + AdUnitParams.MATCHED_CONTENT_ROWS_NUM: rowsNum.toString(), + if (isAdTest != null && isAdTest) AdUnitParams.AD_TEST: 'on', + }; + + /// Creates a configuration object for a Multiplex ad. + /// + /// Arguments: + /// + /// {@macro pkg_google_adsense_parameter_adSlot} + /// {@macro pkg_google_adsense_parameter_adFormat} + /// {@macro pkg_google_adsense_parameter_matchedContentUiType} + /// {@macro pkg_google_adsense_parameter_rowsNum} + /// {@macro pkg_google_adsense_parameter_columnsNum} + /// {@macro pkg_google_adsense_parameter_isFullWidthResponsive} + /// {@macro pkg_google_adsense_parameter_isAdTest} + AdUnitConfiguration.multiplexAdUnit({ + required String adSlot, + required AdFormat adFormat, + MatchedContentUiType? matchedContentUiType, + int? rowsNum, + int? columnsNum, + bool isFullWidthResponsive = true, + bool isAdTest = kDebugMode, + }) : this._internal( + adSlot: adSlot, + adFormat: adFormat, + matchedContentUiType: matchedContentUiType, + rowsNum: rowsNum, + columnsNum: columnsNum, + isFullWidthResponsive: isFullWidthResponsive, + isAdTest: isAdTest); + + /// Creates a configuration object for an In-feed ad. + /// + /// Arguments: + /// + /// {@macro pkg_google_adsense_parameter_adSlot} + /// {@macro pkg_google_adsense_parameter_adLayoutKey} + /// {@macro pkg_google_adsense_parameter_adFormat} + /// {@macro pkg_google_adsense_parameter_isFullWidthResponsive} + /// {@macro pkg_google_adsense_parameter_isAdTest} + AdUnitConfiguration.inFeedAdUnit({ + required String adSlot, + required String adLayoutKey, + AdFormat? adFormat, + bool isFullWidthResponsive = true, + bool isAdTest = kDebugMode, + }) : this._internal( + adSlot: adSlot, + adFormat: adFormat, + adLayoutKey: adLayoutKey, + isFullWidthResponsive: isFullWidthResponsive, + isAdTest: isAdTest); + + /// Creates a configuration object for an In-article ad. + /// + /// Arguments: + /// + /// {@macro pkg_google_adsense_parameter_adSlot} + /// {@macro pkg_google_adsense_parameter_adFormat} + /// {@macro pkg_google_adsense_parameter_adLayout} + /// {@macro pkg_google_adsense_parameter_isFullWidthResponsive} + /// {@macro pkg_google_adsense_parameter_isAdTest} + AdUnitConfiguration.inArticleAdUnit({ + required String adSlot, + AdFormat? adFormat, + AdLayout adLayout = AdLayout.IN_ARTICLE, + bool isFullWidthResponsive = true, + bool isAdTest = kDebugMode, + }) : this._internal( + adSlot: adSlot, + adFormat: adFormat, + adLayout: adLayout, + isFullWidthResponsive: isFullWidthResponsive, + isAdTest: isAdTest); + + /// Creates a configuration object for a Display ad. + /// + /// Arguments: + /// + /// {@macro pkg_google_adsense_parameter_adSlot} + /// {@macro pkg_google_adsense_parameter_adFormat} + /// {@macro pkg_google_adsense_parameter_isFullWidthResponsive} + /// {@macro pkg_google_adsense_parameter_isAdTest} + AdUnitConfiguration.displayAdUnit({ + required String adSlot, + AdFormat? adFormat, + bool isFullWidthResponsive = true, + bool isAdTest = kDebugMode, + }) : this._internal( + adSlot: adSlot, + adFormat: adFormat, + isFullWidthResponsive: isFullWidthResponsive, + isAdTest: isAdTest); + + Map _adUnitParams; + + /// `Map` representation of this configuration object. + Map get params => _adUnitParams; +} diff --git a/packages/google_adsense/lib/src/ad_unit_params.dart b/packages/google_adsense/lib/src/adsense/ad_unit_params.dart similarity index 93% rename from packages/google_adsense/lib/src/ad_unit_params.dart rename to packages/google_adsense/lib/src/adsense/ad_unit_params.dart index 71a537fad8c4..d31a7c2c5fa7 100644 --- a/packages/google_adsense/lib/src/ad_unit_params.dart +++ b/packages/google_adsense/lib/src/adsense/ad_unit_params.dart @@ -7,7 +7,7 @@ class AdUnitParams { /// Identifies AdSense publisher account. Should be passed on initialization static const String AD_CLIENT = 'adClient'; - /// Identified specific ad unit from AdSense console. Can be taken from the ad unit HTML snippet under `data-ad-slot` parameter + /// Identifies a specific ad unit from AdSense console. Can be taken from the ad unit HTML snippet under `data-ad-slot` parameter static const String AD_SLOT = 'adSlot'; /// (Optional) Specify a general shape (desktop only) (horizontal, vertical, and/or rectangle) that your ad unit should conform to @@ -43,9 +43,9 @@ class AdUnitParams { static const String AD_TEST = 'adtest'; } -/// Possible values for [AdUnitParams.AD_FORMAT]. +/// Specifies the general shape that the ad unit should conform to. /// -/// See [docs](https://support.google.com/adsense/answer/9183460?hl=en&ref_topic=9183242&sjid=2004567335727763076-EU#:~:text=Specify%20a%20general%20shape%20(desktop%20only)) for details +/// See [docs](https://support.google.com/adsense/answer/9183460?hl=en&ref_topic=9183242&sjid=2004567335727763076-EU#:~:text=Specify%20a%20general%20shape%20(desktop%20only)) for details. enum AdFormat { /// Default which enables the auto-sizing behavior for the responsive ad unit AUTO('auto'), @@ -81,8 +81,7 @@ enum AdFormat { String toString() => _adFormat; } -/// Possible values for [AdUnitParams.AD_LAYOUT]. -/// +/// Controls the general layout of an in-feed/in-article ad unit. // TODO(sokoloff06): find docs link! enum AdLayout { /// @@ -107,9 +106,9 @@ enum AdLayout { String toString() => _adLayout; } -/// Possible values for [AdUnitParams.MATCHED_CONTENT_UI_TYPE]. +/// Controls the arrangement of the text and images in a Multiplex ad unit. /// -/// See [docs](https://support.google.com/adsense/answer/7533385?hl=en#:~:text=Change%20the%20layout%20of%20your%20Multiplex%20ad%20unit) +/// See [docs](https://support.google.com/adsense/answer/7533385?hl=en#:~:text=Change%20the%20layout%20of%20your%20Multiplex%20ad%20unit). enum MatchedContentUiType { /// In this layout, the image and text appear alongside each other. IMAGE_CARD_SIDEBYSIDE('image_card_sidebyside'), diff --git a/packages/google_adsense/lib/src/ad_unit_widget.dart b/packages/google_adsense/lib/src/adsense/ad_unit_widget.dart similarity index 83% rename from packages/google_adsense/lib/src/ad_unit_widget.dart rename to packages/google_adsense/lib/src/adsense/ad_unit_widget.dart index 97d42fbca45a..6771c468cc1c 100644 --- a/packages/google_adsense/lib/src/ad_unit_widget.dart +++ b/packages/google_adsense/lib/src/adsense/ad_unit_widget.dart @@ -8,21 +8,38 @@ import 'dart:js_interop_unsafe'; import 'package:flutter/widgets.dart'; import 'package:web/web.dart' as web; -import '../experimental/google_adsense.dart'; -import 'js_interop/adsbygoogle.dart'; -import 'logging.dart'; - -/// Widget displaying an ad unit +import '../core/google_adsense.dart'; +import '../js_interop/adsbygoogle.dart'; +import '../utils/logging.dart'; +import 'ad_unit_configuration.dart'; +import 'ad_unit_params.dart'; +import 'adsense_js_interop.dart'; + +/// Widget displaying an ad unit. +/// +/// See the [AdUnitConfiguration] object for a complete reference of the available +/// parameters. +/// +/// When specifying an [AdFormat], the widget will behave like a "responsive" +/// ad unit. Responsive ad units might attempt to overflow the container in which +/// they're rendered. +/// +/// To create a fully constrained widget (specific width/height), do *not* pass +/// an `AdFormat` value, and wrap the `AdUnitWidget` in a constrained box from +/// Flutter, like a [Container] or [SizedBox]. class AdUnitWidget extends StatefulWidget { /// Constructs [AdUnitWidget] - const AdUnitWidget({ + AdUnitWidget({ super.key, - required String adClient, required AdUnitConfiguration configuration, - }) : _adClient = adClient, - _adUnitConfiguration = configuration; + @visibleForTesting String? adClient, + }) : _adClient = adClient ?? adSense.adClient, + _adUnitConfiguration = configuration { + assert(_adClient != null, + 'Attempted to render an AdUnitWidget before calling adSense.initialize'); + } - final String _adClient; + final String? _adClient; final AdUnitConfiguration _adUnitConfiguration; diff --git a/packages/google_adsense/lib/src/adsense/adsense.dart b/packages/google_adsense/lib/src/adsense/adsense.dart new file mode 100644 index 000000000000..07781299c901 --- /dev/null +++ b/packages/google_adsense/lib/src/adsense/adsense.dart @@ -0,0 +1,7 @@ +// 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. + +export 'ad_unit_configuration.dart'; +export 'ad_unit_params.dart' hide AdStatus, AdUnitParams; +export 'ad_unit_widget.dart'; diff --git a/packages/google_adsense/lib/src/adsense/adsense_js_interop.dart b/packages/google_adsense/lib/src/adsense/adsense_js_interop.dart new file mode 100644 index 000000000000..d3b6fa53bb21 --- /dev/null +++ b/packages/google_adsense/lib/src/adsense/adsense_js_interop.dart @@ -0,0 +1,15 @@ +// 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. + +import 'dart:js_interop'; + +import '../js_interop/adsbygoogle.dart'; + +/// Adds a `requestAd` method to request an AdSense ad. +extension AdsByGoogleExtension on AdsByGoogle { + /// Convenience method for invoking push() with an empty object + void requestAd() { + push(JSObject()); + } +} diff --git a/packages/google_adsense/lib/src/adsense_stub.dart b/packages/google_adsense/lib/src/adsense_stub.dart deleted file mode 100644 index d01bc3304445..000000000000 --- a/packages/google_adsense/lib/src/adsense_stub.dart +++ /dev/null @@ -1,28 +0,0 @@ -// 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. - -import 'package:flutter/widgets.dart'; - -import '../experimental/google_adsense.dart'; - -/// A singleton instance of AdSense library public interface. -final AdSense adSense = AdSense(); - -/// AdSense package interface. -class AdSense { - /// Initialization API. Should be called ASAP, ideally in the main method of your app. - void initialize( - String adClient, { - @visibleForTesting bool skipJsLoader = false, - @visibleForTesting Object? jsLoaderTarget, - }) { - throw UnsupportedError('Only supported on web'); - } - - /// Returns a configurable [AdUnitWidget]
- /// `configuration`: see [AdUnitConfiguration] - Widget adUnit(AdUnitConfiguration configuration) { - throw UnsupportedError('Only supported on web'); - } -} diff --git a/packages/google_adsense/lib/src/adsense_web.dart b/packages/google_adsense/lib/src/core/google_adsense.dart similarity index 71% rename from packages/google_adsense/lib/src/adsense_web.dart rename to packages/google_adsense/lib/src/core/google_adsense.dart index 79434b1b616c..3a56b5af92dc 100644 --- a/packages/google_adsense/lib/src/adsense_web.dart +++ b/packages/google_adsense/lib/src/core/google_adsense.dart @@ -7,57 +7,47 @@ import 'dart:js_interop'; import 'package:flutter/widgets.dart'; import 'package:web/web.dart' as web; -import 'ad_unit_configuration.dart'; -import 'ad_unit_widget.dart'; -import 'js_interop/adsbygoogle.dart' show adsbygooglePresent; -import 'js_interop/package_web_tweaks.dart'; +import '../js_interop/adsbygoogle.dart' show adsbygooglePresent; +import '../js_interop/package_web_tweaks.dart'; -import 'logging.dart'; +import '../utils/logging.dart'; -/// Returns a singleton instance of Adsense library public interface -final AdSense adSense = AdSense(); - -/// Main class to work with the library +/// The web implementation of the AdSense API. class AdSense { bool _isInitialized = false; - /// The ad client ID used by this client. - late String _adClient; + /// The [Publisher ID](https://support.google.com/adsense/answer/2923881). + late String adClient; static const String _url = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-'; /// Initializes the AdSense SDK with your [adClient]. /// - /// Should be called ASAP, ideally in the main method of your app. + /// The [adClient] parameter is your AdSense [Publisher ID](https://support.google.com/adsense/answer/2923881). /// - /// Noops after the first call. - void initialize( + /// Should be called ASAP, ideally in the `main` method. + Future initialize( String adClient, { @visibleForTesting bool skipJsLoader = false, @visibleForTesting web.HTMLElement? jsLoaderTarget, - }) { + }) async { if (_isInitialized) { debugLog('adSense.initialize called multiple times. Skipping init.'); return; } - _adClient = adClient; + this.adClient = adClient; if (!(skipJsLoader || _sdkAlreadyLoaded(testingTarget: jsLoaderTarget))) { - _loadJsSdk(_adClient, jsLoaderTarget); + _loadJsSdk(adClient, jsLoaderTarget); } else { debugLog('SDK already on page. Skipping init.'); } _isInitialized = true; } - /// Returns an [AdUnitWidget] with the specified [configuration]. - Widget adUnit(AdUnitConfiguration configuration) { - return AdUnitWidget(adClient: _adClient, configuration: configuration); - } - bool _sdkAlreadyLoaded({ web.HTMLElement? testingTarget, }) { - final String selector = 'script[src*=ca-pub-$_adClient]'; + final String selector = 'script[src*=ca-pub-$adClient]'; return adsbygooglePresent || web.document.querySelector(selector) != null || testingTarget?.querySelector(selector) != null; @@ -91,3 +81,6 @@ class AdSense { (testingTarget ?? web.document.head)!.appendChild(script); } } + +/// The singleton instance of the AdSense SDK. +final AdSense adSense = AdSense(); diff --git a/packages/google_adsense/lib/src/js_interop/adsbygoogle.dart b/packages/google_adsense/lib/src/js_interop/adsbygoogle.dart index 1cdf98d6eadc..bf97d23e978c 100644 --- a/packages/google_adsense/lib/src/js_interop/adsbygoogle.dart +++ b/packages/google_adsense/lib/src/js_interop/adsbygoogle.dart @@ -9,16 +9,8 @@ import 'dart:js_interop'; /// JS-interop mappings to the window.adsbygoogle object. extension type AdsByGoogle._(JSObject _) implements JSObject { - @JS('push') - external void _push(JSObject params); -} - -/// Convenience methods for Dart users. -extension AdsByGoogleExtension on AdsByGoogle { - /// Convenience method for invoking push() with an empty object - void requestAd() { - _push(JSObject()); - } + /// Pushes some `params` to the `window.adsbygoogle` object. + external void push(JSObject params); } // window.adsbygoogle may be null if this package runs before the JS SDK loads. diff --git a/packages/google_adsense/lib/src/logging.dart b/packages/google_adsense/lib/src/utils/logging.dart similarity index 100% rename from packages/google_adsense/lib/src/logging.dart rename to packages/google_adsense/lib/src/utils/logging.dart diff --git a/packages/google_adsense/pubspec.yaml b/packages/google_adsense/pubspec.yaml index e9db445d5121..5739583d2689 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.0.1 +version: 0.0.2 environment: sdk: ^3.4.0