Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[interactive_media_ads] Adds a contribution doc #7460

Merged
merged 19 commits into from
Aug 21, 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.1.2+3

* Adds a contribution guide. See `CONTRIBUTING.md`.

## 0.1.2+2

* Removes dependency on org.jetbrains.kotlin:kotlin-bom.
Expand Down
239 changes: 239 additions & 0 deletions packages/interactive_media_ads/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Contributing to `interactive_media_ads`

Please start by taking a look at the general guide to contributing to the `flutter/packages` repo:
https://github.com/flutter/packages/blob/main/CONTRIBUTING.md

## Package Structure

The structure of this plugin is similar to a [federated plugin](https://docs.flutter.dev/packages-and-plugins/developing-packages#federated-plugins),
except the code for each package (platform interface, platform implementations, and app-facing
interface) are maintained in this single plugin. The sections below will provide an overview of how
this plugin implements each portion.

If you are familiar with [changing federated plugin](https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins)
in the `flutter/packages` repo, the process is similar except that all changes are made in this
plugin. Therefore, it is not necessary to run the script that makes dependencies path based.

### Quick Overview

This plugin uses the native [IMA SDKs] for Android and iOS. The API for the SDK of both platforms
are relatively similar, so this plugin attempts to maintain an interface that is similar to the
native SDKs.

The app-facing interface uses delegation to interact with the underlying platform implementations.
Therefore, the platform interface is similar to the app-facing interface with the differences being
explained in the sections below. Many app-facing interface classes will contain a `platform` field
that is used to forward handling to the platform implementation:

```dart
// App-facing class used by apps
class AdsLoader {
AdsLoader.fromPlatform(this.platform);

final PlatformAdsLoader platform;

Future<void> requestAds(AdsRequest request) {
return platform.requestAds(request);
}
}

// Platform interface class implemented by each platform
abstract base class PlatformAdsLoader {
Future<void> requestAds(AdsRequest request);
}
```

The `platform` variable should also be used to provide access to platform specific methods or
platform specific creation parameters:

```dart
final AdsLoader loader = AdsLoader();
(loader.platform as AndroidAdsLoader).callAndroidSpecificMethod();
```

The other classes/enums included in the app-facing interface are typically exported from the
platform interface. A data class being a good example of a class that is exported.

### Platform Interface

Code location: `lib/src/platform_interface/`.

This declares an interface that each platform must implement to be supported by the app-facing
interface.

The design of the platform interface should prioritize:
* Minimizing the chances of needing a breaking change when adding a new feature.
* Allowing platform implementations to easily add platform specific features.
* Being straight-forward to write unit tests.

Each platform creates a subclass of the central [InteractiveMediaAdsPlatform](lib/src/platform_interface/interactive_media_ads_platform.dart)
class. A platform implementation is set by setting `InteractiveMediaAdsPlatform.instance` to an
instance of a platform implementation of `InteractiveMediaAdsPlatform`.

### Platform Interface Class Types

Below are some of the types of classes in the interface.

#### Delegate Platform Class

These are classes where the app-facing interface needs to delegate handling to the platform
implementation. These classes are typically prefixed with `Platform`.

If the corresponding app-facing class can be instantiated by the app (e.g. [AdsLoader]),
the `InteractiveMediaAdsPlatform.instance` field should be used in a factory to instantiate the
correct platform implementation. See [PlatformAdsLoader] as an example. This class should should
also take a creation params class as the only constructor parameter.

If the corresponding app-facing class can't be instantiated by the app (e.g. `AdsManager`), the
class should only have a single protected constructor. See [PlatformAdsManager].

If the corresponding app-facing class needs to be a `Widget` (e.g. [AdDisplayContainer]), this
should follow the same pattern as being instantiable by the app except it should contain a single
method: `Widget build(BuildContext)`. See [PlatformAdDisplayContainer].

**Note**

Every method should contain no more than one parameter. This allows the platform interface and
platform implementations to add new features without requiring a breaking change.

#### Data Classes

These classes contain only fields and no methods. Each data class should be made `@immutable`.

### Platform Implementations

Code location:
* Android: `lib/src/android/`
* iOS: `lib/src/ios/`

The platform implementations create a subclass of `InteractiveMediaAdsPlatform` and implement the
platform classes that are returned by this.

#### SDK Wrappers

The platform implementations use Dart wrappers of their native SDKs. The SDKs are wrapped using
using the `pigeon` package. However, the code that handles generating the wrappers are still in the
process of review, so this plugin must use a git dependency in the pubspec.

The wrappers for the SDK of each platform can be updated and modified by changing the pigeon files:

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

The generated files are located:
* Android:
* `lib/src/android/interactive_media_ads.g.dart`
* `android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/InteractiveMediaAdsLibrary.g.kt`
* iOS
* `lib/src/ios/interactive_media_ads.g.dart`
* `ios/interactive_media_ads/Sources/interactive_media_ads/InteractiveMediaAdsLibrary.g.swift`

To update a wrapper for a platform, follow the steps:

##### 1. Ensure the project has been built at least once

* Android: Run `flutter build apk --debug` in `example/`.
* iOS: Run `flutter build ios --simulator` in `example/`

##### 2. Add the correct `pigeon` package to `dev_dependencies` in the `pubspec.yaml` and run `pub upgrade`

Android:

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

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]

##### 5. 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

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
IDE:

* Android: Open `example/android/` in a separate Android Studio project.
* iOS: Open `example/ios/` in Xcode.

##### 7. Write API tests

Assuming a non-static method or constructor was added to the native wrapper, a native test will need
to be added.

* Android native tests location: `android/src/test/kotlin/dev/flutter/packages/interactive_media_ads/`
* iOS native tests location `example/ios/RunnerTests/`

#### Dart Unit Testing

Tests for the platform implementations use [mockito] to generate mock objects of the native Dart
wrappers. To generate the mock objects in `test/`, run
`dart run build_runner build --delete-conflicting-outputs`.

### App-facing Interface

Code location: `lib/src/`

The app-facing interface shares the same structure as the platform interface and uses delegation
to forward handling to the platform implementation. Note a few differences from the platform
interface:

* Constructors and methods can contain more than one parameter.
* Platform classes can be instantiated with a platform implementation or creation params of
the corresponding platform interface class. See `AdsLoader.fromPlatform` and
`AdsLoader.fromPlatformCreationParams`.

## Recommended Process for Adding a New Feature

### 1. Create a new feature request issue in the `flutter/flutter` repo.

See https://github.com/flutter/flutter/issues/new?assignees=&labels=&projects=&template=3_feature_request.yml

### 2. In that issue add the specific native classes/methods that this feature requires for each platform:

* [Android SDK]
* [iOS SDK]

Add a note if this feature only exist for a single platform.

### 3. Add a design where the feature can be added to the platform interface and app-facing interface.

If this is only supported on a single platform, add where it can be added in the platform
implementation.

### 4. Work can be started on the feature request or you can wait for feedback from a Flutter contributor.

[IMA SDKs]: https://developers.google.com/interactive-media-ads
[AdsLoader]: lib/src/ads_loader.dart
[AdDisplayContainer]: lib/src/ad_display_container.dart
[PlatformAdsLoader]: lib/src/platform_interface/platform_ads_loader.dart
[PlatformAdsManager]: lib/src/platform_interface/platform_ads_manager.dart
[PlatformAdDisplayContainer]: lib/src/platform_interface/platform_ad_display_container.dart
[Android SDK]: https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/api/reference/com/google/ads/interactivemedia/v3/api/package-summary
[iOS SDK]: https://developers.google.com/interactive-media-ads/docs/sdks/ios/client-side/reference/Classes
[mockito]: https://pub.dev/packages/mockito
8 changes: 6 additions & 2 deletions packages/interactive_media_ads/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ a separate video player positioned on top of the app's content video player.

**NOTE:**
* The initial release for this package supports linear pre-roll video ads on iOS and Android
* platforms.
platforms.
* Companion ads, Background Audio ads and Google Dynamic Ad Insertion methods are currently not
* supported.
supported.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Slight README fix.

cc @harold1208


## IMA client-side overview

Expand Down Expand Up @@ -272,6 +272,10 @@ void dispose() {
That's it! You're now requesting and displaying ads with the IMA SDK. To learn about additional SDK
features, see the [API reference](https://pub.dev/documentation/interactive_media_ads/latest/).

## Contributing

For information on contributing to this plugin, see [`CONTRIBUTING.md`](CONTRIBUTING.md).

[1]: https://developers.google.com/interactive-media-ads
[2]: https://www.iab.com/guidelines/vast/
[3]: https://pub.dev/documentation/interactive_media_ads/latest/interactive_media_ads/AdDisplayContainer-class.html
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.1.2+2"
const val pluginVersion = "0.1.2+3"
}

override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) {
Expand Down
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.1.2+2"
static let pluginVersion = "0.1.2+3"

func pigeonDefaultConstructor(
pigeonApi: PigeonApiIMAAdsRequest, adTagUrl: String, adDisplayContainer: IMAAdDisplayContainer,
Expand Down
2 changes: 1 addition & 1 deletion packages/interactive_media_ads/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: interactive_media_ads
description: A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS.
repository: https://github.com/flutter/packages/tree/main/packages/interactive_media_ads
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+interactive_media_ads%22
version: 0.1.2+2 # This must match the version in
version: 0.1.2+3 # This must match the version in
# `android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt` and
# `ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift`

Expand Down