Skip to content

Commit

Permalink
[interactive_media_ads] Adds internal wrapper for iOS native `IMAComp…
Browse files Browse the repository at this point in the history
…anionAd` (#7873)
  • Loading branch information
bparrishMines authored Oct 17, 2024
1 parent 5582669 commit a99f9e6
Show file tree
Hide file tree
Showing 12 changed files with 392 additions and 51 deletions.
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: [email protected]: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

0 comments on commit a99f9e6

Please sign in to comment.