Skip to content

[interactive_media_ads] Adds internal wrapper for iOS native IMACompanionAd #7873

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

Merged
merged 10 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.2+12

* Adds internal wrapper for iOS native `IMACompanionAd`.

## 0.2.2+11

* Adds internal wrapper for Android native `UniversalAdId`.
Expand Down
39 changes: 10 additions & 29 deletions packages/interactive_media_ads/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,40 +135,21 @@ To update a wrapper for a platform, follow the steps:
* Android: Run `flutter build apk --debug` in `example/`.
* iOS: Run `flutter build ios --simulator` in `example/`

##### 2. Ensure the correct `pigeon` package is added to `dev_dependencies` in the `pubspec.yaml` and run `pub upgrade`
##### 2. Make changes to the respective pigeon file that matches the native SDK

Android:

```yaml
pigeon: ^22.2.0
```

iOS:

```yaml
pigeon:
git:
url: git@github.com:bparrishMines/packages.git
ref: pigeon_wrapper_swift
path: packages/pigeon
```

##### 3. Uncomment the multiline comments in the pigeon file

* Android: `pigeons/interactive_media_ads_android.dart`
* iOS: `pigeons/interactive_media_ads_ios.dart`

##### 4. Make changes that match the native SDK

* [Android SDK]
* [iOS SDK]
* Android:
- [Android SDK]
- Pigeon file to update: `pigeons/interactive_media_ads_android.dart`
* iOS:
- [iOS SDK]
- Pigeon file to update: `pigeons/interactive_media_ads_ios.dart`

##### 5. Run the code generator from the terminal
##### 3. Run the code generator from the terminal

* Android: `dart run pigeon --input pigeons/interactive_media_ads_android.dart`
* iOS: `dart run pigeon --input pigeons/interactive_media_ads_ios.dart`

##### 6. Update the generated APIs in native code
##### 4. Update the generated APIs in native code

Running the `flutter build` step from step 1 again should provide build errors and indicate what
needs to be done. Alternatively, it can be easier to update native code with the platform's specific
Expand All @@ -177,7 +158,7 @@ IDE:
* Android: Open `example/android/` in a separate Android Studio project.
* iOS: Open `example/ios/` in Xcode.

##### 7. Write API tests
##### 5. Write API tests

Assuming a non-static method or constructor was added to the native wrapper, a native test will need
to be added.
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.2+11"
const val pluginVersion = "0.2.2+12"
}

override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
8F599BBB2C332C010090A0DF /* AdsRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F599BBA2C332C010090A0DF /* AdsRequestTests.swift */; };
8F599BBD2C332CFE0090A0DF /* ContentPlayheadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F599BBC2C332CFE0090A0DF /* ContentPlayheadTests.swift */; };
8F599BBF2C3335B40090A0DF /* ViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F599BBE2C3335B40090A0DF /* ViewControllerTests.swift */; };
8F8382A32CBDB4A4007F28E0 /* CompanionAdProxyApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F8382A22CBDB4A4007F28E0 /* CompanionAdProxyApiTests.swift */; };
8F977DCF2C2B99C600A90D4B /* AdDisplayContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F977DCE2C2B99C600A90D4B /* AdDisplayContainerTests.swift */; };
8F977DD32C2BA15100A90D4B /* TestProxyApiRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F977DD22C2BA15100A90D4B /* TestProxyApiRegistrar.swift */; };
8F977DD52C2C777600A90D4B /* AdErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F977DD42C2C777600A90D4B /* AdErrorTests.swift */; };
Expand Down Expand Up @@ -78,6 +79,7 @@
8F599BBA2C332C010090A0DF /* AdsRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsRequestTests.swift; sourceTree = "<group>"; };
8F599BBC2C332CFE0090A0DF /* ContentPlayheadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPlayheadTests.swift; sourceTree = "<group>"; };
8F599BBE2C3335B40090A0DF /* ViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerTests.swift; sourceTree = "<group>"; };
8F8382A22CBDB4A4007F28E0 /* CompanionAdProxyApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompanionAdProxyApiTests.swift; sourceTree = "<group>"; };
8F977DCE2C2B99C600A90D4B /* AdDisplayContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdDisplayContainerTests.swift; sourceTree = "<group>"; };
8F977DD22C2BA15100A90D4B /* TestProxyApiRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProxyApiRegistrar.swift; sourceTree = "<group>"; };
8F977DD42C2C777600A90D4B /* AdErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdErrorTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -133,6 +135,7 @@
8F599BBA2C332C010090A0DF /* AdsRequestTests.swift */,
8F599BBC2C332CFE0090A0DF /* ContentPlayheadTests.swift */,
8F599BBE2C3335B40090A0DF /* ViewControllerTests.swift */,
8F8382A22CBDB4A4007F28E0 /* CompanionAdProxyApiTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -411,6 +414,7 @@
buildActionMask = 2147483647;
files = (
8F599BBF2C3335B40090A0DF /* ViewControllerTests.swift in Sources */,
8F8382A32CBDB4A4007F28E0 /* CompanionAdProxyApiTests.swift in Sources */,
8FC919922CA5D86F00188068 /* FriendlyObstructionTests.swift in Sources */,
8F977DD92C2C8C6A00A90D4B /* AdLoadingErrorDataTests.swift in Sources */,
8F599BB32C2DD87D0090A0DF /* AdsLoaderTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 GoogleInteractiveMediaAds
import XCTest

@testable import interactive_media_ads

class CompanionAdProxyApiTests: XCTestCase {
func testResourceValue() {
let registrar = TestProxyApiRegistrar()
let api = registrar.apiDelegate.pigeonApiIMACompanionAd(registrar)

let instance = TestCompanionAd.customInit()
let value = try? api.pigeonDelegate.resourceValue(pigeonApi: api, pigeonInstance: instance)

XCTAssertEqual(value, instance.resourceValue)
}

func testApiFramework() {
let registrar = TestProxyApiRegistrar()
let api = registrar.apiDelegate.pigeonApiIMACompanionAd(registrar)

let instance = TestCompanionAd.customInit()
let value = try? api.pigeonDelegate.apiFramework(pigeonApi: api, pigeonInstance: instance)

XCTAssertEqual(value, instance.apiFramework)
}

func testWidth() {
let registrar = TestProxyApiRegistrar()
let api = registrar.apiDelegate.pigeonApiIMACompanionAd(registrar)

let instance = TestCompanionAd.customInit()
let value = try? api.pigeonDelegate.width(pigeonApi: api, pigeonInstance: instance)

XCTAssertEqual(value, Int64(instance.width))
}

func testHeight() {
let registrar = TestProxyApiRegistrar()
let api = registrar.apiDelegate.pigeonApiIMACompanionAd(registrar)

let instance = TestCompanionAd.customInit()
let value = try? api.pigeonDelegate.height(pigeonApi: api, pigeonInstance: instance)

XCTAssertEqual(value, Int64(instance.height))
}

}

class TestCompanionAd: IMACompanionAd {
// Workaround to subclass an Objective-C class that has an `init` constructor with NS_UNAVAILABLE
static func customInit() -> TestCompanionAd {
let instance =
TestCompanionAd.perform(NSSelectorFromString("new")).takeRetainedValue() as! TestCompanionAd
return instance
}

override var resourceValue: String? {
return "resourceValue"
}

override var apiFramework: String? {
return "apiFramework"
}

override var width: Int {
return 0
}

override var height: Int {
return 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class AdsRequestProxyAPIDelegate: PigeonApiDelegateIMAAdsRequest {
/// The current version of the `interactive_media_ads` plugin.
///
/// This must match the version in pubspec.yaml.
static let pluginVersion = "0.2.2+11"
static let pluginVersion = "0.2.2+12"

func pigeonDefaultConstructor(
pigeonApi: PigeonApiIMAAdsRequest, adTagUrl: String, adDisplayContainer: IMAAdDisplayContainer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 Foundation
import GoogleInteractiveMediaAds

/// ProxyApi implementation for [IMACompanionAd].
///
/// 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 CompanionAdProxyAPIDelegate: PigeonApiDelegateIMACompanionAd {
func resourceValue(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws
-> String?
{
return pigeonInstance.resourceValue
}

func apiFramework(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws
-> String?
{
return pigeonInstance.apiFramework
}

func width(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws -> Int64 {
return Int64(pigeonInstance.width)
}

func height(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws -> Int64 {
return Int64(pigeonInstance.height)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ protocol InteractiveMediaAdsLibraryPigeonProxyApiDelegate {
func pigeonApiIMAFriendlyObstruction(
_ registrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar
) -> PigeonApiIMAFriendlyObstruction
/// An implementation of [PigeonApiIMACompanionAd] used to add a new Dart instance of
/// `IMACompanionAd` to the Dart `InstanceManager` and make calls to Dart.
func pigeonApiIMACompanionAd(_ registrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar)
-> PigeonApiIMACompanionAd
}

extension InteractiveMediaAdsLibraryPigeonProxyApiDelegate {
Expand Down Expand Up @@ -769,8 +773,20 @@ private class InteractiveMediaAdsLibraryPigeonInternalProxyApiCodecReaderWriter:
return
}

if let instance = value as? NSObject {
pigeonRegistrar.apiDelegate.pigeonApiNSObject(pigeonRegistrar).pigeonNewInstance(
if let instance = value as? IMAFriendlyObstruction {
pigeonRegistrar.apiDelegate.pigeonApiIMAFriendlyObstruction(pigeonRegistrar)
.pigeonNewInstance(
pigeonInstance: instance
) { _ in }
super.writeByte(128)
super.writeValue(
pigeonRegistrar.instanceManager.identifierWithStrongReference(
forInstance: instance as AnyObject)!)
return
}

if let instance = value as? IMACompanionAd {
pigeonRegistrar.apiDelegate.pigeonApiIMACompanionAd(pigeonRegistrar).pigeonNewInstance(
pigeonInstance: instance
) { _ in }
super.writeByte(128)
Expand All @@ -780,11 +796,10 @@ private class InteractiveMediaAdsLibraryPigeonInternalProxyApiCodecReaderWriter:
return
}

if let instance = value as? IMAFriendlyObstruction {
pigeonRegistrar.apiDelegate.pigeonApiIMAFriendlyObstruction(pigeonRegistrar)
.pigeonNewInstance(
pigeonInstance: instance
) { _ in }
if let instance = value as? NSObject {
pigeonRegistrar.apiDelegate.pigeonApiNSObject(pigeonRegistrar).pigeonNewInstance(
pigeonInstance: instance
) { _ in }
super.writeByte(128)
super.writeValue(
pigeonRegistrar.instanceManager.identifierWithStrongReference(
Expand Down Expand Up @@ -3156,6 +3171,11 @@ protocol PigeonApiProtocolIMAFriendlyObstruction {
final class PigeonApiIMAFriendlyObstruction: PigeonApiProtocolIMAFriendlyObstruction {
unowned let pigeonRegistrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar
let pigeonDelegate: PigeonApiDelegateIMAFriendlyObstruction
///An implementation of [NSObject] used to access callback methods
var pigeonApiNSObject: PigeonApiNSObject {
return pigeonRegistrar.apiDelegate.pigeonApiNSObject(pigeonRegistrar)
}

init(
pigeonRegistrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar,
delegate: PigeonApiDelegateIMAFriendlyObstruction
Expand Down Expand Up @@ -3244,3 +3264,86 @@ final class PigeonApiIMAFriendlyObstruction: PigeonApiProtocolIMAFriendlyObstruc
}
}
}
protocol PigeonApiDelegateIMACompanionAd {
/// The value for the resource of this companion.
func resourceValue(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws
-> String?
/// The API needed to execute this ad, or nil if unavailable.
func apiFramework(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws
-> String?
/// The width of the companion in pixels.
///
/// 0 if unavailable.
func width(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws -> Int64
/// The height of the companion in pixels.
///
/// 0 if unavailable.
func height(pigeonApi: PigeonApiIMACompanionAd, pigeonInstance: IMACompanionAd) throws -> Int64
}

protocol PigeonApiProtocolIMACompanionAd {
}

final class PigeonApiIMACompanionAd: PigeonApiProtocolIMACompanionAd {
unowned let pigeonRegistrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar
let pigeonDelegate: PigeonApiDelegateIMACompanionAd
///An implementation of [NSObject] used to access callback methods
var pigeonApiNSObject: PigeonApiNSObject {
return pigeonRegistrar.apiDelegate.pigeonApiNSObject(pigeonRegistrar)
}

init(
pigeonRegistrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar,
delegate: PigeonApiDelegateIMACompanionAd
) {
self.pigeonRegistrar = pigeonRegistrar
self.pigeonDelegate = delegate
}
///Creates a Dart instance of IMACompanionAd and attaches it to [pigeonInstance].
func pigeonNewInstance(
pigeonInstance: IMACompanionAd, completion: @escaping (Result<Void, PigeonError>) -> Void
) {
if pigeonRegistrar.ignoreCallsToDart {
completion(
.failure(
PigeonError(
code: "ignore-calls-error",
message: "Calls to Dart are being ignored.", details: "")))
return
}
if pigeonRegistrar.instanceManager.containsInstance(pigeonInstance as AnyObject) {
completion(.success(Void()))
return
}
let pigeonIdentifierArg = pigeonRegistrar.instanceManager.addHostCreatedInstance(
pigeonInstance as AnyObject)
let resourceValueArg = try! pigeonDelegate.resourceValue(
pigeonApi: self, pigeonInstance: pigeonInstance)
let apiFrameworkArg = try! pigeonDelegate.apiFramework(
pigeonApi: self, pigeonInstance: pigeonInstance)
let widthArg = try! pigeonDelegate.width(pigeonApi: self, pigeonInstance: pigeonInstance)
let heightArg = try! pigeonDelegate.height(pigeonApi: self, pigeonInstance: pigeonInstance)
let binaryMessenger = pigeonRegistrar.binaryMessenger
let codec = pigeonRegistrar.codec
let channelName: String =
"dev.flutter.pigeon.interactive_media_ads.IMACompanionAd.pigeon_newInstance"
let channel = FlutterBasicMessageChannel(
name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage(
[pigeonIdentifierArg, resourceValueArg, apiFrameworkArg, widthArg, heightArg] as [Any?]
) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(Void()))
}
}
}
}
Loading