Skip to content

[interactive_media_ads] Adds support for companion ads #9260

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

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c77279b
add last few companion ad methods
bparrishMines Apr 29, 2025
520789c
platform interface and app facing impl
bparrishMines Apr 29, 2025
e0e71c7
android impl
bparrishMines Apr 29, 2025
2d3e352
switch to build widget creation params
bparrishMines Apr 30, 2025
0c7e9b4
add example and fix android
bparrishMines Apr 30, 2025
693912d
ios attempt
bparrishMines May 1, 2025
dde9140
trying some fixes
bparrishMines May 1, 2025
69fe7f0
nothing working yet
bparrishMines May 1, 2025
387cb25
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines May 16, 2025
196b597
eureka
bparrishMines May 16, 2025
9cee787
revert to other sample ad tag
bparrishMines May 20, 2025
bc2d96b
use general settings
bparrishMines May 20, 2025
3874e7e
update slot
bparrishMines May 20, 2025
26982a1
update
bparrishMines May 20, 2025
50ff6c4
quality update
bparrishMines May 20, 2025
6b303ff
analyze errors
bparrishMines May 20, 2025
6b92bbe
missing docs
bparrishMines May 20, 2025
65ad934
change style
bparrishMines May 20, 2025
dd6b2d4
add onclicked
bparrishMines May 20, 2025
4c8c61d
weird import
bparrishMines May 20, 2025
0db2d51
use weak references
bparrishMines May 20, 2025
a9a7553
undo project changes
bparrishMines May 20, 2025
6b43a29
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines May 22, 2025
4136840
add platform tests
bparrishMines May 29, 2025
bfc6586
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines May 29, 2025
0841b0e
analyze errors
bparrishMines May 30, 2025
f85d2b6
android test
bparrishMines May 30, 2025
17b6d31
ios tests
bparrishMines May 30, 2025
6d00ba4
improve docs
bparrishMines May 30, 2025
2528c34
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines May 30, 2025
aa6dfcb
version bump
bparrishMines May 30, 2025
9b902da
separate example app
bparrishMines Jun 1, 2025
baa39c1
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines Jun 1, 2025
69596f7
accidental g
bparrishMines Jun 1, 2025
2289ac4
fix flake
bparrishMines Jun 2, 2025
640736d
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines Jun 2, 2025
cd27ff1
docs and unused code
bparrishMines Jun 3, 2025
8348733
Merge branch 'main' of github.com:flutter/packages into companion_ads
bparrishMines Jun 3, 2025
a3357d8
analyze warning
bparrishMines Jun 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/interactive_media_ads/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.4

* Adds support for companion ads. See `CompanionAdSlot` and `AdDisplayContainer(companionAds)`.

## 0.2.3+12

* Fixes appending request agent to ad tags that contain a query.
Expand Down
12 changes: 6 additions & 6 deletions packages/interactive_media_ads/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ If building on Android, add the user permissions required by the IMA SDK for req
Add the import statements for the `interactive_media_ads` and [video_player][7]. Both plugins should
already be added to your `pubspec.yaml`.

<?code-excerpt "example/lib/main.dart (imports)"?>
<?code-excerpt "example/lib/readme_example.dart (imports)"?>
```dart
import 'package:interactive_media_ads/interactive_media_ads.dart';
import 'package:video_player/video_player.dart';
Expand All @@ -66,7 +66,7 @@ import 'package:video_player/video_player.dart';
Create a new [StatefulWidget](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html)
that handles displaying Ads and playing content.

<?code-excerpt "example/lib/main.dart (example_widget)"?>
<?code-excerpt "example/lib/readme_example.dart (example_widget)"?>
```dart
/// Example widget displaying an Ad before a video.
class AdExampleWidget extends StatefulWidget {
Expand Down Expand Up @@ -120,7 +120,7 @@ Instantiate the [AdDisplayContainer][3] for playing Ads and the
[VideoPlayerController](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController-class.html)
for playing content.

<?code-excerpt "example/lib/main.dart (ad_and_content_players)"?>
<?code-excerpt "example/lib/readme_example.dart (ad_and_content_players)"?>
```dart
late final AdDisplayContainer _adDisplayContainer = AdDisplayContainer(
onContainerAdded: (AdDisplayContainer container) {
Expand Down Expand Up @@ -194,7 +194,7 @@ void initState() {

Return a `Widget` that contains the ad player and the content player.

<?code-excerpt "example/lib/main.dart (widget_build)"?>
<?code-excerpt "example/lib/readme_example.dart (widget_build)"?>
```dart
@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -244,7 +244,7 @@ Widget build(BuildContext context) {

Handle requesting ads and add event listeners to handle when content should be displayed or hidden.

<?code-excerpt "example/lib/main.dart (request_ads)"?>
<?code-excerpt "example/lib/readme_example.dart (request_ads)"?>
```dart
Future<void> _requestAds(AdDisplayContainer container) {
return _adsLoader.requestAds(AdsRequest(
Expand Down Expand Up @@ -292,7 +292,7 @@ Future<void> _pauseContent() {

Dispose the content player and destroy the [AdsManager][6].

<?code-excerpt "example/lib/main.dart (dispose)"?>
<?code-excerpt "example/lib/readme_example.dart (dispose)"?>
```dart
@override
void dispose() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AdsRequestProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
*
* This must match the version in pubspec.yaml.
*/
const val pluginVersion = "0.2.3+12"
const val pluginVersion = "0.2.4"
}

override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@

package dev.flutter.packages.interactive_media_ads

import com.google.ads.interactivemedia.v3.api.BaseDisplayContainer
import com.google.ads.interactivemedia.v3.api.CompanionAdSlot

/**
* ProxyApi implementation for [com.google.ads.interactivemedia.v3.api.BaseDisplayContainer].
*
* <p>This class may handle instantiating native object instances that are attached to a Dart
* instance or handle method calls on the associated native class or an instance of that class.
*/
class BaseDisplayContainerProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
PigeonApiBaseDisplayContainer(pigeonRegistrar)
PigeonApiBaseDisplayContainer(pigeonRegistrar) {
override fun setCompanionSlots(
pigeon_instance: BaseDisplayContainer,
companionSlots: List<CompanionAdSlot>?
) {
return pigeon_instance.setCompanionSlots(companionSlots)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ class CompanionAdSlotProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
override fun setSize(pigeon_instance: CompanionAdSlot, width: Long, height: Long) {
pigeon_instance.setSize(width.toInt(), height.toInt())
}

override fun setFluidSize(pigeon_instance: CompanionAdSlot) {
pigeon_instance.setSize(CompanionAdSlot.FLUID_SIZE, CompanionAdSlot.FLUID_SIZE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.google.ads.interactivemedia.v3.api.AdDisplayContainer
import com.google.ads.interactivemedia.v3.api.AdsLoader
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings
import com.google.ads.interactivemedia.v3.api.AdsRequest
import com.google.ads.interactivemedia.v3.api.CompanionAdSlot
import com.google.ads.interactivemedia.v3.api.ImaSdkFactory
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings
import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer
Expand All @@ -32,6 +33,10 @@ class ImaSdkFactoryProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
return ImaSdkFactory.createAdDisplayContainer(container, player)
}

override fun createCompanionAdSlot(pigeon_instance: ImaSdkFactory): CompanionAdSlot {
return pigeon_instance.createCompanionAdSlot()
}

override fun createImaSdkSettings(pigeon_instance: ImaSdkFactory): ImaSdkSettings {
return pigeon_instance.createImaSdkSettings()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +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.
// Autogenerated from Pigeon (v25.3.1), do not edit directly.
// Autogenerated from Pigeon (v25.3.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

Expand Down Expand Up @@ -387,9 +387,7 @@ abstract class InteractiveMediaAdsLibraryPigeonProxyApiRegistrar(
* An implementation of [PigeonApiBaseDisplayContainer] used to add a new Dart instance of
* `BaseDisplayContainer` to the Dart `InstanceManager`.
*/
open fun getPigeonApiBaseDisplayContainer(): PigeonApiBaseDisplayContainer {
return PigeonApiBaseDisplayContainer(this)
}
abstract fun getPigeonApiBaseDisplayContainer(): PigeonApiBaseDisplayContainer

/**
* An implementation of [PigeonApiAdDisplayContainer] used to add a new Dart instance of
Expand Down Expand Up @@ -592,6 +590,8 @@ abstract class InteractiveMediaAdsLibraryPigeonProxyApiRegistrar(
fun setUp() {
InteractiveMediaAdsLibraryPigeonInstanceManagerApi.setUpMessageHandlers(
binaryMessenger, instanceManager)
PigeonApiBaseDisplayContainer.setUpMessageHandlers(
binaryMessenger, getPigeonApiBaseDisplayContainer())
PigeonApiAdsLoader.setUpMessageHandlers(binaryMessenger, getPigeonApiAdsLoader())
PigeonApiAdsRequest.setUpMessageHandlers(binaryMessenger, getPigeonApiAdsRequest())
PigeonApiContentProgressProvider.setUpMessageHandlers(
Expand Down Expand Up @@ -621,6 +621,7 @@ abstract class InteractiveMediaAdsLibraryPigeonProxyApiRegistrar(

fun tearDown() {
InteractiveMediaAdsLibraryPigeonInstanceManagerApi.setUpMessageHandlers(binaryMessenger, null)
PigeonApiBaseDisplayContainer.setUpMessageHandlers(binaryMessenger, null)
PigeonApiAdsLoader.setUpMessageHandlers(binaryMessenger, null)
PigeonApiAdsRequest.setUpMessageHandlers(binaryMessenger, null)
PigeonApiContentProgressProvider.setUpMessageHandlers(binaryMessenger, null)
Expand Down Expand Up @@ -1004,9 +1005,55 @@ private open class InteractiveMediaAdsLibraryPigeonCodec : StandardMessageCodec(
* https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/BaseDisplayContainer.html.
*/
@Suppress("UNCHECKED_CAST")
open class PigeonApiBaseDisplayContainer(
abstract class PigeonApiBaseDisplayContainer(
open val pigeonRegistrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar
) {
/**
* Sets slots for displaying companions.
*
* Passing null will reset the container to having no companion slots.
*/
abstract fun setCompanionSlots(
pigeon_instance: com.google.ads.interactivemedia.v3.api.BaseDisplayContainer,
companionSlots: List<com.google.ads.interactivemedia.v3.api.CompanionAdSlot>?
)

companion object {
@Suppress("LocalVariableName")
fun setUpMessageHandlers(
binaryMessenger: BinaryMessenger,
api: PigeonApiBaseDisplayContainer?
) {
val codec = api?.pigeonRegistrar?.codec ?: InteractiveMediaAdsLibraryPigeonCodec()
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.interactive_media_ads.BaseDisplayContainer.setCompanionSlots",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pigeon_instanceArg =
args[0] as com.google.ads.interactivemedia.v3.api.BaseDisplayContainer
val companionSlotsArg =
args[1] as List<com.google.ads.interactivemedia.v3.api.CompanionAdSlot>?
val wrapped: List<Any?> =
try {
api.setCompanionSlots(pigeon_instanceArg, companionSlotsArg)
listOf(null)
} catch (exception: Throwable) {
InteractiveMediaAdsLibraryPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}

@Suppress("LocalVariableName", "FunctionName")
/** Creates a Dart instance of BaseDisplayContainer and attaches it to [pigeon_instanceArg]. */
fun pigeon_newInstance(
Expand Down Expand Up @@ -2267,6 +2314,11 @@ abstract class PigeonApiImaSdkFactory(
player: com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer
): com.google.ads.interactivemedia.v3.api.AdDisplayContainer

/** Creates a CompanionAdSlot for the SDK to fill with companion ads. */
abstract fun createCompanionAdSlot(
pigeon_instance: com.google.ads.interactivemedia.v3.api.ImaSdkFactory
): com.google.ads.interactivemedia.v3.api.CompanionAdSlot

/** Creates an `ImaSdkSettings` object for configuring the IMA SDK. */
abstract fun createImaSdkSettings(
pigeon_instance: com.google.ads.interactivemedia.v3.api.ImaSdkFactory
Expand Down Expand Up @@ -2343,6 +2395,28 @@ abstract class PigeonApiImaSdkFactory(
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.interactive_media_ads.ImaSdkFactory.createCompanionAdSlot",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pigeon_instanceArg = args[0] as com.google.ads.interactivemedia.v3.api.ImaSdkFactory
val wrapped: List<Any?> =
try {
listOf(api.createCompanionAdSlot(pigeon_instanceArg))
} catch (exception: Throwable) {
InteractiveMediaAdsLibraryPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
Expand Down Expand Up @@ -5561,6 +5635,14 @@ abstract class PigeonApiCompanionAdSlot(
height: Long
)

/**
* Sets the size of the slot as fluid.
*
* This is a convenience method that sets both parameters of [setSize] to
* [CompanionAdSlot.FLUID_SIZE](https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/CompanionAdSlot#FLUID_SIZE()).
*/
abstract fun setFluidSize(pigeon_instance: com.google.ads.interactivemedia.v3.api.CompanionAdSlot)

companion object {
@Suppress("LocalVariableName")
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiCompanionAdSlot?) {
Expand Down Expand Up @@ -5760,6 +5842,30 @@ abstract class PigeonApiCompanionAdSlot(
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.interactive_media_ads.CompanionAdSlot.setFluidSize",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pigeon_instanceArg =
args[0] as com.google.ads.interactivemedia.v3.api.CompanionAdSlot
val wrapped: List<Any?> =
try {
api.setFluidSize(pigeon_instanceArg)
listOf(null)
} catch (exception: Throwable) {
InteractiveMediaAdsLibraryPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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.

package dev.flutter.packages.interactive_media_ads

import com.google.ads.interactivemedia.v3.api.BaseDisplayContainer
import com.google.ads.interactivemedia.v3.api.CompanionAdSlot
import kotlin.test.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify

class BaseDisplayContainerProxyApiTest {
@Test
fun setCompanionSlots() {
val api = TestProxyApiRegistrar().getPigeonApiBaseDisplayContainer()

val instance = mock<BaseDisplayContainer>()
val companionSlots = listOf(mock<CompanionAdSlot>())
api.setCompanionSlots(instance, companionSlots)

verify(instance).setCompanionSlots(companionSlots)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,14 @@ class CompanionAdSlotProxyApiTest {

verify(instance).setSize(width.toInt(), height.toInt())
}

@Test
fun setFluidSize() {
val api = TestProxyApiRegistrar().getPigeonApiCompanionAdSlot()

val instance = mock<CompanionAdSlot>()
api.setFluidSize(instance)

verify(instance).setSize(CompanionAdSlot.FLUID_SIZE, CompanionAdSlot.FLUID_SIZE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.google.ads.interactivemedia.v3.api.AdDisplayContainer
import com.google.ads.interactivemedia.v3.api.AdsLoader
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings
import com.google.ads.interactivemedia.v3.api.AdsRequest
import com.google.ads.interactivemedia.v3.api.CompanionAdSlot
import com.google.ads.interactivemedia.v3.api.ImaSdkFactory
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings
import kotlin.test.Test
Expand Down Expand Up @@ -42,6 +43,17 @@ class ImaSdkFactoryProxyApiTest {
assertEquals(mockAdsLoader, api.createAdsLoader(instance, mockSettings, mockContainer))
}

@Test
fun createCompanionAdSlot() {
val api = TestProxyApiRegistrar().getPigeonApiImaSdkFactory()

val instance = mock<ImaSdkFactory>()
val mockAdSlot = mock<CompanionAdSlot>()
whenever(instance.createCompanionAdSlot()).thenReturn(mockAdSlot)

assertEquals(mockAdSlot, api.createCompanionAdSlot(instance))
}

@Test
fun createAdsRequest() {
val api = TestProxyApiRegistrar().getPigeonApiImaSdkFactory()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 Flutter
import XCTest

@testable import interactive_media_ads

final class ViewTests: XCTestCase {
func testPigeonDefaultConstructor() {
let registrar = TestProxyApiRegistrar()
let api = registrar.apiDelegate.pigeonApiUIView(registrar)

let instance = try? api.pigeonDelegate.pigeonDefaultConstructor(
pigeonApi: api)

XCTAssertNotNil(instance)
}
}
Loading