diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java index 09fd2a7d4..7da154c24 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java @@ -68,6 +68,7 @@ class AdMessageCodec extends StandardMessageCodec { private static final byte VALUE_AD_MANAGER_AD_VIEW_OPTIONS = (byte) 154; private static final byte VALUE_BANNER_PARAMETERS = (byte) 155; private static final byte VALUE_CUSTOM_PARAMETERS = (byte) 156; + private static final byte VALUE_NATIVE_PARAMETERS = (byte) 157; @NonNull Context context; @NonNull final FlutterAdSize.AdSizeFactory adSizeFactory; @@ -259,6 +260,12 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) { FlutterCustomParameters customParameters = (FlutterCustomParameters) value; writeValue(stream, customParameters.formatIds); writeValue(stream, customParameters.viewOptions); + } else if (value instanceof FlutterNativeParameters) { + stream.write(VALUE_NATIVE_PARAMETERS); + FlutterNativeParameters nativeParameters = (FlutterNativeParameters) value; + writeValue(stream, nativeParameters.factoryId); + writeValue(stream, nativeParameters.nativeAdOptions); + writeValue(stream, nativeParameters.viewOptions); } else { super.writeValue(stream, value); } @@ -429,6 +436,11 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) { return new FlutterCustomParameters( (List) readValueOfType(buffer.get(), buffer), (Map) readValueOfType(buffer.get(), buffer)); + case VALUE_NATIVE_PARAMETERS: + return new FlutterNativeParameters( + (String) readValueOfType(buffer.get(), buffer), + (FlutterNativeAdOptions) readValueOfType(buffer.get(), buffer), + (Map) readValueOfType(buffer.get(), buffer)); default: return super.readValueOfType(type, buffer); } diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java index 89ec55302..c2716ed63 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java @@ -147,7 +147,8 @@ public void loadAdLoaderAd( @NonNull AdListener adListener, @NonNull AdRequest request, @Nullable FlutterAdLoaderAd.BannerParameters bannerParameters, - @Nullable FlutterAdLoaderAd.CustomParameters customParameters) { + @Nullable FlutterAdLoaderAd.CustomParameters customParameters, + @Nullable FlutterAdLoaderAd.NativeParameters nativeParameters) { AdLoader.Builder builder = new AdLoader.Builder(context, adUnitId); if (bannerParameters != null) { builder = builder.forAdManagerAdView(bannerParameters.listener, bannerParameters.adSizes); @@ -160,6 +161,12 @@ public void loadAdLoaderAd( builder = builder.forCustomFormatAd(formatId, customParameters.listener, null); } } + if (nativeParameters != null) { + builder = builder.forNativeAd(nativeParameters.listener); + if (nativeParameters.nativeAdOptions != null) { + builder = builder.withNativeAdOptions(nativeParameters.nativeAdOptions); + } + } builder.withAdListener(adListener).build().loadAd(request); } @@ -169,7 +176,8 @@ public void loadAdManagerAdLoaderAd( @NonNull AdListener adListener, @NonNull AdManagerAdRequest adManagerAdRequest, @Nullable FlutterAdLoaderAd.BannerParameters bannerParameters, - @Nullable FlutterAdLoaderAd.CustomParameters customParameters) { + @Nullable FlutterAdLoaderAd.CustomParameters customParameters, + @Nullable FlutterAdLoaderAd.NativeParameters nativeParameters) { AdLoader.Builder builder = new AdLoader.Builder(context, adUnitId); if (bannerParameters != null) { builder = builder.forAdManagerAdView(bannerParameters.listener, bannerParameters.adSizes); @@ -182,6 +190,12 @@ public void loadAdManagerAdLoaderAd( builder = builder.forCustomFormatAd(formatId, customParameters.listener, null); } } + if (nativeParameters != null) { + builder = builder.forNativeAd(nativeParameters.listener); + if (nativeParameters.nativeAdOptions != null) { + builder = builder.withNativeAdOptions(nativeParameters.nativeAdOptions); + } + } builder.withAdListener(adListener).build().loadAd(adManagerAdRequest); } } diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java index 56d3c89d7..ea5b5f496 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java @@ -24,10 +24,14 @@ import com.google.android.gms.ads.admanager.AdManagerAdView; import com.google.android.gms.ads.formats.AdManagerAdViewOptions; import com.google.android.gms.ads.formats.OnAdManagerAdViewLoadedListener; +import com.google.android.gms.ads.nativead.NativeAd; +import com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener; +import com.google.android.gms.ads.nativead.NativeAdOptions; import com.google.android.gms.ads.nativead.NativeCustomFormatAd; import com.google.android.gms.ads.nativead.NativeCustomFormatAd.OnCustomFormatAdLoadedListener; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.CustomAdFactory; +import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.NativeAdFactory; import java.util.Map; /** @@ -35,7 +39,9 @@ * instances served for a single {@link AdRequest} or {@link AdManagerAdRequest} */ class FlutterAdLoaderAd extends FlutterAd - implements OnAdManagerAdViewLoadedListener, OnCustomFormatAdLoadedListener { + implements OnAdManagerAdViewLoadedListener, + OnCustomFormatAdLoadedListener, + OnNativeAdLoadedListener { private static final String TAG = "FlutterAdLoaderAd"; @NonNull private final AdInstanceManager manager; @@ -48,6 +54,7 @@ class FlutterAdLoaderAd extends FlutterAd @Nullable private View view; @Nullable protected BannerParameters bannerParameters; @Nullable protected CustomParameters customParameters; + @Nullable protected NativeParameters nativeParameters; static class Builder { @Nullable private AdInstanceManager manager; @@ -59,6 +66,8 @@ static class Builder { @Nullable private FlutterBannerParameters bannerParameters; @Nullable private FlutterCustomParameters customParameters; @Nullable private Map customFactories; + @Nullable private FlutterNativeParameters nativeParameters; + @Nullable private Map nativeFactories; public Builder setId(int id) { this.id = id; @@ -106,6 +115,17 @@ public Builder withAvailableCustomFactories( return this; } + public Builder setNative(@Nullable FlutterNativeParameters nativeParameters) { + this.nativeParameters = nativeParameters; + return this; + } + + public Builder withAvailableNativeFactories( + @NonNull Map nativeFactories) { + this.nativeFactories = nativeFactories; + return this; + } + FlutterAdLoaderAd build() { if (manager == null) { throw new IllegalStateException("manager must be provided"); @@ -139,6 +159,12 @@ FlutterAdLoaderAd build() { new FlutterCustomFormatAdLoadedListener(adLoaderAd), customFactories); } + if (nativeParameters != null) { + adLoaderAd.nativeParameters = + nativeParameters.asNativeParameters( + new FlutterNativeAdLoadedListener(adLoaderAd), nativeFactories); + } + return adLoaderAd; } } @@ -147,6 +173,7 @@ enum AdLoaderAdType { UNKNOWN, BANNER, CUSTOM, + NATIVE, } static class BannerParameters { @@ -179,6 +206,24 @@ static class CustomParameters { } } + static class NativeParameters { + @NonNull final OnNativeAdLoadedListener listener; + @NonNull final NativeAdFactory factory; + @Nullable final NativeAdOptions nativeAdOptions; + @Nullable final Map viewOptions; + + NativeParameters( + @NonNull OnNativeAdLoadedListener listener, + @NonNull NativeAdFactory factory, + @Nullable NativeAdOptions nativeAdOptions, + @Nullable Map viewOptions) { + this.listener = listener; + this.factory = factory; + this.nativeAdOptions = nativeAdOptions; + this.viewOptions = viewOptions; + } + } + protected FlutterAdLoaderAd( int adId, @NonNull AdInstanceManager manager, @@ -220,7 +265,12 @@ void load() { // As of 20.0.0 of GMA, mockito is unable to mock AdLoader. if (request != null) { adLoader.loadAdLoaderAd( - adUnitId, adListener, request.asAdRequest(adUnitId), bannerParameters, customParameters); + adUnitId, + adListener, + request.asAdRequest(adUnitId), + bannerParameters, + customParameters, + nativeParameters); return; } @@ -230,7 +280,8 @@ void load() { adListener, adManagerRequest.asAdManagerAdRequest(adUnitId), bannerParameters, - customParameters); + customParameters, + nativeParameters); return; } @@ -281,6 +332,14 @@ public void onCustomFormatAdLoaded(@NonNull NativeCustomFormatAd ad) { manager.onAdLoaded(adId, null); } + @Override + public void onNativeAdLoaded(@NonNull NativeAd ad) { + view = nativeParameters.factory.createNativeAd(ad, nativeParameters.viewOptions); + type = AdLoaderAdType.NATIVE; + ad.setOnPaidEventListener(new FlutterPaidEventListener(manager, this)); + manager.onAdLoaded(adId, ad.getResponseInfo()); + } + @Override void dispose() { if (view == null) { diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java index 3086c7084..a3f481bca 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java @@ -20,10 +20,10 @@ class FlutterCustomParameters { FlutterAdLoaderAd.CustomParameters asCustomParameters( @NonNull OnCustomFormatAdLoadedListener listener, - @NonNull Map availableFactories) { + @NonNull Map registeredFactories) { Map factories = new HashMap<>(); for (String formatId : formatIds) { - factories.put(formatId, availableFactories.get(formatId)); + factories.put(formatId, registeredFactories.get(formatId)); } return new FlutterAdLoaderAd.CustomParameters(listener, factories, viewOptions); } diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java new file mode 100644 index 000000000..43a6348b7 --- /dev/null +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java @@ -0,0 +1,48 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener; +import com.google.android.gms.ads.nativead.NativeAdOptions; +import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.NativeAdFactory; +import java.util.Map; + +class FlutterNativeParameters { + @NonNull final String factoryId; + @Nullable final FlutterNativeAdOptions nativeAdOptions; + @Nullable final Map viewOptions; + + FlutterNativeParameters( + @NonNull String factoryId, + @Nullable FlutterNativeAdOptions nativeAdOptions, + @Nullable Map viewOptions) { + this.factoryId = factoryId; + this.nativeAdOptions = nativeAdOptions; + this.viewOptions = viewOptions; + } + + FlutterAdLoaderAd.NativeParameters asNativeParameters( + @NonNull OnNativeAdLoadedListener listener, + @NonNull Map registeredFactories) { + NativeAdOptions nativeAdOptions = null; + if (this.nativeAdOptions != null) { + nativeAdOptions = this.nativeAdOptions.asNativeAdOptions(); + } + return new FlutterAdLoaderAd.NativeParameters( + listener, registeredFactories.get(factoryId), nativeAdOptions, viewOptions); + } +} diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java index 03e50adb6..e24539cba 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java @@ -502,6 +502,17 @@ public void onAdInspectorClosed(@Nullable AdInspectorError adInspectorError) { } } + final FlutterNativeParameters nativeParameters = + call.argument("native"); + if (nativeParameters != null) { + if (nativeAdFactories.get(nativeParameters.factoryId) == null) { + final String message = + String.format("Can't find NativeAdFactory with id: %s", nativeParameters.factoryId); + result.error("AdLoaderAdError", message, null); + return; + } + } + final FlutterAdLoaderAd adLoaderAd = new FlutterAdLoaderAd.Builder() .setManager(instanceManager) @@ -516,6 +527,8 @@ public void onAdInspectorClosed(@Nullable AdInspectorError adInspectorError) { .setBanner(call.argument("banner")) .setCustom(customParameters) .withAvailableCustomFactories(customAdFactories) + .setNative(nativeParameters) + .withAvailableNativeFactories(nativeAdFactories) .build(); instanceManager.trackAd(adLoaderAd, call.argument("adId")); adLoaderAd.load(); diff --git a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java index a6574dbde..c1004465e 100644 --- a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java +++ b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java @@ -576,4 +576,27 @@ public void encodeCustomParameters() { assertEquals(result.viewOptions.size(), 1); assertEquals(result.viewOptions.get("key"), "value"); } + + @Test + public void encodeNativeParameters() { + final ByteBuffer data = + codec.encodeMessage( + new FlutterNativeParameters( + "factory-id", + new FlutterNativeAdOptions(1, 1, null, true, true, true), + Collections.singletonMap("key", "value"))); + + final FlutterNativeParameters result = + (FlutterNativeParameters) codec.decodeMessage((ByteBuffer) data.position(0)); + + assertEquals(result.factoryId, "factory-id"); + assertEquals(result.nativeAdOptions.adChoicesPlacement, Integer.valueOf(1)); + assertEquals(result.nativeAdOptions.mediaAspectRatio, Integer.valueOf(1)); + assertNull(result.nativeAdOptions.videoOptions); + assertTrue(result.nativeAdOptions.requestCustomMuteThisAd); + assertTrue(result.nativeAdOptions.shouldRequestMultipleImages); + assertTrue(result.nativeAdOptions.shouldReturnUrlsForImageAssets); + assertEquals(result.viewOptions.size(), 1); + assertEquals(result.viewOptions.get("key"), "value"); + } } diff --git a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java index 95624efe3..9d42eed32 100644 --- a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java +++ b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java @@ -30,18 +30,22 @@ import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.AdValue; import com.google.android.gms.ads.LoadAdError; import com.google.android.gms.ads.ResponseInfo; import com.google.android.gms.ads.admanager.AdManagerAdRequest; import com.google.android.gms.ads.admanager.AdManagerAdView; +import com.google.android.gms.ads.nativead.NativeAd; import com.google.android.gms.ads.nativead.NativeCustomFormatAd; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.googlemobileads.FlutterAd.FlutterLoadAdError; import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.CustomAdFactory; +import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.NativeAdFactory; import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; @@ -88,13 +92,13 @@ public Object answer(InvocationOnMock invocation) { }) .when(mockLoader) .loadAdManagerAdLoaderAd( - eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull()); + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); adLoaderAd.load(); verify(mockLoader) .loadAdManagerAdLoaderAd( - eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull()); + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); verify(testManager).onAdClicked(eq(1)); verify(testManager).onAdClosed(eq(1)); @@ -132,12 +136,14 @@ public Object answer(InvocationOnMock invocation) { } }) .when(mockLoader) - .loadAdLoaderAd(eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull()); + .loadAdLoaderAd( + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); adLoaderAd.load(); verify(mockLoader) - .loadAdLoaderAd(eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull()); + .loadAdLoaderAd( + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); verify(testManager).onAdClicked(eq(1)); verify(testManager).onAdClosed(eq(1)); @@ -189,7 +195,12 @@ public Object answer(InvocationOnMock invocation) { }) .when(mockLoader) .loadAdManagerAdLoaderAd( - eq("testId"), any(AdListener.class), eq(mockRequest), eq(bannerParameters), isNull()); + eq("testId"), + any(AdListener.class), + eq(mockRequest), + eq(bannerParameters), + isNull(), + isNull()); adLoaderAd.load(); @@ -203,7 +214,12 @@ public Object answer(InvocationOnMock invocation) { verify(mockLoader) .loadAdManagerAdLoaderAd( - eq("testId"), any(AdListener.class), eq(mockRequest), eq(bannerParameters), isNull()); + eq("testId"), + any(AdListener.class), + eq(mockRequest), + eq(bannerParameters), + isNull(), + isNull()); verify(testManager).onAdClicked(eq(1)); verify(testManager).onAdClosed(eq(1)); @@ -256,7 +272,12 @@ public Object answer(InvocationOnMock invocation) { }) .when(mockLoader) .loadAdManagerAdLoaderAd( - eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), eq(customParameters)); + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + eq(customParameters), + isNull()); adLoaderAd.load(); @@ -266,7 +287,12 @@ public Object answer(InvocationOnMock invocation) { verify(mockLoader) .loadAdManagerAdLoaderAd( - eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), eq(customParameters)); + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + eq(customParameters), + isNull()); verify(testManager).onAdClicked(eq(1)); verify(testManager).onAdClosed(eq(1)); @@ -277,6 +303,100 @@ public Object answer(InvocationOnMock invocation) { verify(testManager).onAdLoaded(eq(1), isNull()); } + @Test + public void loadAdLoaderAdNativeWithAdManagerAdRequest() { + final FlutterAdManagerAdRequest mockFlutterRequest = mock(FlutterAdManagerAdRequest.class); + final AdManagerAdRequest mockRequest = mock(AdManagerAdRequest.class); + when(mockFlutterRequest.asAdManagerAdRequest(anyString())).thenReturn(mockRequest); + FlutterAdLoader mockLoader = mock(FlutterAdLoader.class); + final FlutterAdLoaderAd adLoaderAd = + new FlutterAdLoaderAd(1, testManager, "testId", mockFlutterRequest, mockLoader); + final FlutterNativeAdLoadedListener listener = new FlutterNativeAdLoadedListener(adLoaderAd); + final NativeAdFactory mockNativeAdFactory = mock(NativeAdFactory.class); + final FlutterAdLoaderAd.NativeParameters nativeParameters = + new FlutterAdLoaderAd.NativeParameters(listener, mockNativeAdFactory, null, null); + adLoaderAd.nativeParameters = nativeParameters; + + final LoadAdError mockLoadAdError = mock(LoadAdError.class); + when(mockLoadAdError.getCode()).thenReturn(1); + when(mockLoadAdError.getDomain()).thenReturn("2"); + when(mockLoadAdError.getMessage()).thenReturn("3"); + + final ResponseInfo mockResponseInfo = mock(ResponseInfo.class); + final NativeAd mockNativeAd = mock(NativeAd.class); + when(mockNativeAd.getResponseInfo()).thenReturn(mockResponseInfo); + + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + AdListener listener = invocation.getArgument(1); + listener.onAdClicked(); + listener.onAdClosed(); + listener.onAdFailedToLoad(mockLoadAdError); + listener.onAdImpression(); + listener.onAdOpened(); + + FlutterAdLoaderAd.NativeParameters nativeParameters = invocation.getArgument(5); + nativeParameters.listener.onNativeAdLoaded(mockNativeAd); + return null; + } + }) + .when(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + isNull(), + eq(nativeParameters)); + + final AdValue mockAdValue = mock(AdValue.class); + when(mockAdValue.getCurrencyCode()).thenReturn("Dollars"); + when(mockAdValue.getPrecisionType()).thenReturn(1); + when(mockAdValue.getValueMicros()).thenReturn(1000L); + + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + FlutterPaidEventListener listener = invocation.getArgument(0); + listener.onPaidEvent(mockAdValue); + return null; + } + }) + .when(mockNativeAd) + .setOnPaidEventListener(any(FlutterPaidEventListener.class)); + + adLoaderAd.load(); + + assertEquals(adLoaderAd.getAdLoaderAdType(), FlutterAdLoaderAd.AdLoaderAdType.NATIVE); + + verify(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + isNull(), + eq(nativeParameters)); + + verify(testManager).onAdClicked(eq(1)); + verify(testManager).onAdClosed(eq(1)); + FlutterLoadAdError expectedError = new FlutterLoadAdError(mockLoadAdError); + verify(testManager).onAdFailedToLoad(eq(1), eq(expectedError)); + verify(testManager).onAdImpression(eq(1)); + verify(testManager).onAdOpened(eq(1)); + verify(testManager).onAdLoaded(eq(1), eq(mockResponseInfo)); + + final ArgumentCaptor adValueCaptor = + ArgumentCaptor.forClass(FlutterAdValue.class); + verify(testManager).onPaidEvent(eq(adLoaderAd), adValueCaptor.capture()); + assertEquals(adValueCaptor.getValue().currencyCode, "Dollars"); + assertEquals(adValueCaptor.getValue().precisionType, 1); + assertEquals(adValueCaptor.getValue().valueMicros, 1000L); + } + @Test(expected = IllegalStateException.class) public void adLoaderAdBuilderNullManager() { new FlutterAdLoaderAd.Builder() diff --git a/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m b/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m index 483f89c89..a1199e1a5 100644 --- a/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m +++ b/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m @@ -31,7 +31,8 @@ - (void)testDelegates { rootViewController:viewController adId:@0 banner:nil - custom:nil]; + custom:nil + native:nil]; ad.manager = manager; @@ -60,7 +61,8 @@ - (void)testBannerDelegates { adId:@0 banner:[[FLTBannerParameters alloc] initWithSizes:@[ adSize ] options:nil] - custom:nil]; + custom:nil + native:nil]; ad.manager = manager; @@ -134,7 +136,8 @@ - (void)testCustomDelegates { rootViewController:viewController adId:@0 banner:nil - custom:custom]; + custom:custom + native:nil]; ad.manager = manager; @@ -179,6 +182,61 @@ - (void)testCustomDelegates { OCMVerify([manager onCustomNativeAdDidDismissScreen:[OCMArg isEqual:ad]]); } +- (void)testNativeDelegates { + UIViewController *viewController = OCMClassMock([UIViewController class]); + FLTAdInstanceManager *manager = OCMClassMock([FLTAdInstanceManager class]); + + FLTNativeParameters *native = + [[FLTNativeParameters alloc] initWithFactoryId:@"factoryId" + nativeAdOptions:nil + viewOptions:nil]; + id factory = + OCMProtocolMock(@protocol(FLTNativeAdFactory)); + native.factory = factory; + + FLTAdLoaderAd *ad = + [[FLTAdLoaderAd alloc] initWithAdUnitId:@"testAdUnitId" + request:[[FLTAdRequest alloc] init] + rootViewController:viewController + adId:@0 + banner:nil + custom:nil + native:native]; + + ad.manager = manager; + + [ad load]; + + // GADNativeAdLoaderDelegate + GADNativeAd *nativeAd = OCMClassMock([GADNativeAd class]); + + [ad adLoader:ad.adLoader didReceiveNativeAd:nativeAd]; + + XCTAssertEqual([ad adLoaderAdType], FLTAdLoaderAdTypeNative); + + OCMVerify([nativeAd setDelegate:[OCMArg isEqual:ad]]); + OCMVerify([factory createNativeAd:[OCMArg isEqual:nativeAd] + customOptions:[OCMArg isEqual:nil]]); + OCMVerify([manager onAdLoaded:[OCMArg isEqual:ad] + responseInfo:[OCMArg isEqual:nil]]); + + // GADNativeAdDelegate + [ad nativeAdDidRecordImpression:nativeAd]; + OCMVerify([manager onNativeAdImpression:[OCMArg isEqual:ad]]); + + [ad nativeAdDidRecordClick:nativeAd]; + OCMVerify([manager adDidRecordClick:[OCMArg isEqual:ad]]); + + [ad nativeAdWillPresentScreen:nativeAd]; + OCMVerify([manager onNativeAdWillPresentScreen:[OCMArg isEqual:ad]]); + + [ad nativeAdWillDismissScreen:nativeAd]; + OCMVerify([manager onNativeAdWillDismissScreen:[OCMArg isEqual:ad]]); + + [ad nativeAdDidDismissScreen:nativeAd]; + OCMVerify([manager onNativeAdDidDismissScreen:[OCMArg isEqual:ad]]); +} + - (void)testLoadAdLoaderAd { FLTAdRequest *request = [[FLTAdRequest alloc] init]; request.keywords = @[ @"apple" ]; @@ -199,7 +257,8 @@ - (void)testLoadAdLoaderAd:(FLTAdRequest *)request { rootViewController:viewController adId:@1 banner:nil - custom:nil]; + custom:nil + native:nil]; XCTAssertEqual(ad.adLoader.adUnitID, @"testAdUnitId"); XCTAssertEqual(ad.adLoader.delegate, ad); diff --git a/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m b/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m index 71f394193..38ef6c705 100644 --- a/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m +++ b/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m @@ -829,6 +829,41 @@ - (void)testEncodeDecodeCustomParameters { XCTAssertEqual(factories.count, 0); } +- (void)testEncodeDecodeNativeParameters { + FLTNativeParameters *parameters = [[FLTNativeParameters alloc] + initWithFactoryId:@"factory-id" + nativeAdOptions:[[FLTNativeAdOptions alloc] + initWithAdChoicesPlacement:@(1) + mediaAspectRatio:@(1) + videoOptions:nil + requestCustomMuteThisAd:@YES + shouldRequestMultipleImages:@YES + shouldReturnUrlsForImageAssets:@YES] + viewOptions:@{@"key" : @"value"}]; + + NSData *encodedMessage = [_messageCodec encode:parameters]; + + FLTNativeParameters *decodedParameters = + [_messageCodec decode:encodedMessage]; + + XCTAssertEqualObjects(decodedParameters.factoryId, @"factory-id"); + + XCTAssertEqualObjects(parameters.nativeAdOptions.adChoicesPlacement, @(1)); + XCTAssertEqualObjects(parameters.nativeAdOptions.mediaAspectRatio, @(1)); + XCTAssertNil(parameters.nativeAdOptions.videoOptions); + XCTAssertEqualObjects(parameters.nativeAdOptions.requestCustomMuteThisAd, + @(YES)); + XCTAssertEqualObjects(parameters.nativeAdOptions.shouldRequestMultipleImages, + @(YES)); + XCTAssertEqualObjects( + parameters.nativeAdOptions.shouldReturnUrlsForImageAssets, @(YES)); + + NSDictionary *viewOptions = decodedParameters.viewOptions; + + XCTAssertNotNil(viewOptions); + XCTAssertEqualObjects(viewOptions[@"key"], @"value"); +} + @end @implementation FLTTestAdSizeFactory diff --git a/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.h b/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.h index e45ac6cfa..f955c5e9d 100644 --- a/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.h +++ b/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.h @@ -321,6 +321,7 @@ typedef NS_ENUM(NSInteger, FLTAdLoaderAdType) { FLTAdLoaderAdTypeUnknown = 0, FLTAdLoaderAdTypeBanner = 1, FLTAdLoaderAdTypeCustom = 2, + FLTAdLoaderAdTypeNative = 3, }; @interface FLTBannerParameters : NSObject @@ -340,11 +341,23 @@ typedef NS_ENUM(NSInteger, FLTAdLoaderAdType) { viewOptions:(NSDictionary *_Nullable)viewOptions; @end +@interface FLTNativeParameters : NSObject +@property(readonly, nonnull) NSString *factoryId; +@property(readonly, nullable) FLTNativeAdOptions *nativeAdOptions; +@property(readonly, nullable) NSDictionary *viewOptions; +@property(nullable) id factory; +- (nonnull instancetype) + initWithFactoryId:(nonnull NSString *)factoryId + nativeAdOptions:(nullable FLTNativeAdOptions *)nativeAdOptions + viewOptions:(nullable NSDictionary *)viewOptions; +@end + @interface FLTAdLoaderAd : FLTBaseAd + GADCustomNativeAdDelegate, GADNativeAdLoaderDelegate, + GADNativeAdDelegate> @property(readonly, nonnull) GADAdLoader *adLoader; @property(readonly) FLTAdLoaderAdType adLoaderAdType; @property(readonly, nullable) FLTAdSize *adSize; @@ -355,7 +368,8 @@ typedef NS_ENUM(NSInteger, FLTAdLoaderAdType) { rootViewController:(nonnull UIViewController *)rootViewController adId:(nonnull NSNumber *)adId banner:(nullable FLTBannerParameters *)bannerParameters - custom:(nullable FLTCustomParameters *)customParameters; + custom:(nullable FLTCustomParameters *)customParameters + native:(nullable FLTNativeParameters *)nativeParameters; @end @interface FLTRewardItem : NSObject diff --git a/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.m b/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.m index 76a4129d9..8e9718bda 100644 --- a/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.m +++ b/packages/google_mobile_ads/ios/Classes/FLTAd_Internal.m @@ -1237,6 +1237,19 @@ @implementation FLTCustomParameters } @end +@implementation FLTNativeParameters +- (nonnull instancetype) + initWithFactoryId:(nonnull NSString *)factoryId + nativeAdOptions:(nullable FLTNativeAdOptions *)nativeAdOptions + viewOptions:(nullable NSDictionary *)viewOptions { + self = [super init]; + _factoryId = factoryId; + _nativeAdOptions = nativeAdOptions; + _viewOptions = viewOptions; + return self; +} +@end + #pragma mark - FLTAdLoaderAd @implementation FLTAdLoaderAd { @@ -1246,6 +1259,7 @@ @implementation FLTAdLoaderAd { UIView *_view; FLTBannerParameters *_banner; FLTCustomParameters *_custom; + FLTNativeParameters *_native; } - (nonnull instancetype) @@ -1254,7 +1268,8 @@ @implementation FLTAdLoaderAd { rootViewController:(nonnull UIViewController *)rootViewController adId:(nonnull NSNumber *)adId banner:(nullable FLTBannerParameters *)bannerParameters - custom:(nullable FLTCustomParameters *)customParameters { + custom:(nullable FLTCustomParameters *)customParameters + native:(nullable FLTNativeParameters *)nativeParameters { self = [super init]; if (self) { self.adId = adId; @@ -1287,6 +1302,17 @@ @implementation FLTAdLoaderAd { [adTypes addObject:GADAdLoaderAdTypeCustomNative]; } + if (![FLTAdUtil isNull:nativeParameters]) { + _native = nativeParameters; + + [adTypes addObject:GADAdLoaderAdTypeNative]; + + if (![FLTAdUtil isNull:_native.nativeAdOptions]) { + [options + addObjectsFromArray:_native.nativeAdOptions.asGADAdLoaderOptions]; + } + } + _adLoader = [[GADAdLoader alloc] initWithAdUnitID:_adUnitId rootViewController:rootViewController adTypes:adTypes @@ -1440,6 +1466,54 @@ - (void)customNativeAdDidDismissScreen:(nonnull GADCustomNativeAd *)nativeAd { [manager onCustomNativeAdDidDismissScreen:self]; } +#pragma mark - GADNativeAdLoaderDelegate + +- (void)adLoader:(nonnull GADAdLoader *)adLoader + didReceiveNativeAd:(nonnull GADNativeAd *)nativeAd { + // Use Nil instead of Null to fix crash with Swift integrations. + NSDictionary *customOptions = + [[NSNull null] isEqual:_native.viewOptions] ? nil : _native.viewOptions; + _adLoaderAdType = FLTAdLoaderAdTypeNative; + _view = [_native.factory createNativeAd:nativeAd customOptions:customOptions]; + nativeAd.delegate = self; + + __weak FLTAdLoaderAd *weakSelf = self; + nativeAd.paidEventHandler = ^(GADAdValue *_Nonnull value) { + if (weakSelf.manager == nil) { + return; + } + [weakSelf.manager + onPaidEvent:weakSelf + value:[[FLTAdValue alloc] initWithValue:value.value + precision:(NSInteger)value.precision + currencyCode:value.currencyCode]]; + }; + + [manager onAdLoaded:self responseInfo:nativeAd.responseInfo]; +} + +#pragma mark - GADNativeAdDelegate + +- (void)nativeAdDidRecordImpression:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdImpression:self]; +} + +- (void)nativeAdDidRecordClick:(nonnull GADNativeAd *)nativeAd { + [manager adDidRecordClick:self]; +} + +- (void)nativeAdWillPresentScreen:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdWillPresentScreen:self]; +} + +- (void)nativeAdWillDismissScreen:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdWillDismissScreen:self]; +} + +- (void)nativeAdDidDismissScreen:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdDidDismissScreen:self]; +} + #pragma mark - FlutterPlatformView - (nonnull UIView *)view { diff --git a/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsPlugin.m b/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsPlugin.m index 585f12f62..ab9729370 100644 --- a/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsPlugin.m +++ b/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsPlugin.m @@ -439,6 +439,22 @@ - (void)handleMethodCall:(FlutterMethodCall *)call } } + FLTNativeParameters *native = call.arguments[@"native"]; + if ([FLTAdUtil isNotNull:native]) { + id factory = _nativeAdFactories[native.factoryId]; + if (!factory) { + NSString *message = [NSString + stringWithFormat:@"Can't find NativeAdFactory with id: %@", + native.factoryId]; + result([FlutterError errorWithCode:@"AdLoaderAdError" + message:message + details:nil]); + return; + } + + native.factory = factory; + } + FLTAdRequest *request; if ([FLTAdUtil isNotNull:call.arguments[@"request"]]) { request = call.arguments[@"request"]; @@ -452,7 +468,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call rootViewController:rootController adId:call.arguments[@"adId"] banner:call.arguments[@"banner"] - custom:custom]; + custom:custom + native:native]; [_manager loadAd:ad]; result(nil); } else if ([call.method isEqualToString:@"loadInterstitialAd"]) { diff --git a/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsReaderWriter_Internal.m b/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsReaderWriter_Internal.m index 5728fd355..be32e3dbc 100644 --- a/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsReaderWriter_Internal.m +++ b/packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsReaderWriter_Internal.m @@ -49,6 +49,7 @@ typedef NS_ENUM(NSInteger, FLTAdMobField) { FLTAdmobFieldAdManagerAdViewOptions = 154, FLTAdmobBannerParameters = 155, FLTAdmobCustomParameters = 156, + FLTAdmobNativeParameters = 157, }; @interface FLTGoogleMobileAdsWriter : FlutterStandardWriter @@ -346,6 +347,12 @@ - (id _Nullable)readValueOfType:(UInt8)type { initWithFormatIds:[self readValueOfType:[self readByte]] viewOptions:[self readValueOfType:[self readByte]]]; } + case FLTAdmobNativeParameters: { + return [[FLTNativeParameters alloc] + initWithFactoryId:[self readValueOfType:[self readByte]] + nativeAdOptions:[self readValueOfType:[self readByte]] + viewOptions:[self readValueOfType:[self readByte]]]; + } } return [super readValueOfType:type]; } @@ -539,6 +546,12 @@ - (void)writeValue:(id)value { FLTCustomParameters *customParameters = value; [self writeValue:customParameters.formatIds]; [self writeValue:customParameters.viewOptions]; + } else if ([value isKindOfClass:[FLTNativeParameters class]]) { + [self writeByte:FLTAdmobNativeParameters]; + FLTNativeParameters *nativeParameters = value; + [self writeValue:nativeParameters.factoryId]; + [self writeValue:nativeParameters.nativeAdOptions]; + [self writeValue:nativeParameters.viewOptions]; } else { [super writeValue:value]; } diff --git a/packages/google_mobile_ads/lib/src/ad_containers.dart b/packages/google_mobile_ads/lib/src/ad_containers.dart index b26f4fc10..9c0c6bf6f 100644 --- a/packages/google_mobile_ads/lib/src/ad_containers.dart +++ b/packages/google_mobile_ads/lib/src/ad_containers.dart @@ -1085,6 +1085,9 @@ enum AdLoaderAdType { /// Custom ad type custom, + + /// Native ad type + native, } /// An AdLoaderAd. @@ -1122,6 +1125,7 @@ class AdLoaderAd extends AdWithView { required AdRequest request, this.banner, this.custom, + this.native, }) : super(adUnitId: adUnitId, listener: listener) { if (request is AdManagerAdRequest) { adManagerRequest = request; @@ -1146,6 +1150,9 @@ class AdLoaderAd extends AdWithView { /// Optional parameters used to configure served "custom" ads final CustomParameters? custom; + /// Optional parameters used to configure served "native" ads + final NativeParameters? native; + @override Future load() async { await instanceManager.loadAdLoaderAd(this); @@ -1664,6 +1671,37 @@ class CustomParameters { } } +/// Central configuration item for native view requests served by an +/// [AdLoaderAd]. +class NativeParameters { + /// An identifier for the factory that creates the Platform view. + final String factoryId; + + /// Options to configure the native ad request. + final NativeAdOptions? nativeAdOptions; + + /// Optional options used to create the Platform view. + /// + /// These options are passed to the platform's `NativeAdFactory`. + final Map? viewOptions; + + /// Construct a [NativeParameters] instance, used by an [AdLoaderAd] to + /// configure native views. + NativeParameters({ + required this.factoryId, + this.nativeAdOptions, + this.viewOptions, + }); + + @override + bool operator ==(other) { + return other is NativeParameters && + factoryId == other.factoryId && + nativeAdOptions == other.nativeAdOptions && + mapEquals(viewOptions, other.viewOptions); + } +} + /// Used to configure native ad requests. class NativeAdOptions { /// Where to place the AdChoices icon. diff --git a/packages/google_mobile_ads/lib/src/ad_instance_manager.dart b/packages/google_mobile_ads/lib/src/ad_instance_manager.dart index 114bc0516..e16cb7b9a 100644 --- a/packages/google_mobile_ads/lib/src/ad_instance_manager.dart +++ b/packages/google_mobile_ads/lib/src/ad_instance_manager.dart @@ -471,6 +471,8 @@ class AdInstanceManager { return AdLoaderAdType.banner; case 2: return AdLoaderAdType.custom; + case 3: + return AdLoaderAdType.native; default: debugPrint('Error: unknown AdLoaderAdType value: $adLoaderAdType'); return AdLoaderAdType.unknown; @@ -483,6 +485,8 @@ class AdInstanceManager { return AdLoaderAdType.banner; case 2: return AdLoaderAdType.custom; + case 3: + return AdLoaderAdType.native; default: debugPrint('Error: unknown AdLoaderAdType value: $adLoaderAdType'); return AdLoaderAdType.unknown; @@ -589,6 +593,7 @@ class AdInstanceManager { 'adManagerRequest': ad.adManagerRequest, 'banner': ad.banner, 'custom': ad.custom, + 'native': ad.native, }, ); } @@ -904,6 +909,7 @@ class AdMessageCodec extends StandardMessageCodec { static const int _valueAdManagerAdViewOptions = 154; static const int _valueBannerParameters = 155; static const int _valueCustomParameters = 156; + static const int _valueNativeParameters = 157; @override void writeValue(WriteBuffer buffer, dynamic value) { @@ -1041,6 +1047,11 @@ class AdMessageCodec extends StandardMessageCodec { buffer.putUint8(_valueCustomParameters); writeValue(buffer, value.formatIds); writeValue(buffer, value.viewOptions); + } else if (value is NativeParameters) { + buffer.putUint8(_valueNativeParameters); + writeValue(buffer, value.factoryId); + writeValue(buffer, value.nativeAdOptions); + writeValue(buffer, value.viewOptions); } else { super.writeValue(buffer, value); } @@ -1275,6 +1286,13 @@ class AdMessageCodec extends StandardMessageCodec { viewOptions: readValueOfType(buffer.getUint8(), buffer) ?.cast(), ); + case _valueNativeParameters: + return NativeParameters( + factoryId: readValueOfType(buffer.getUint8(), buffer), + nativeAdOptions: readValueOfType(buffer.getUint8(), buffer), + viewOptions: readValueOfType(buffer.getUint8(), buffer) + ?.cast(), + ); default: return super.readValueOfType(type, buffer); } diff --git a/packages/google_mobile_ads/test/ad_loader_ad_test.dart b/packages/google_mobile_ads/test/ad_loader_ad_test.dart index 4b5dea4fa..a8486b708 100644 --- a/packages/google_mobile_ads/test/ad_loader_ad_test.dart +++ b/packages/google_mobile_ads/test/ad_loader_ad_test.dart @@ -68,6 +68,7 @@ void main() { 'adManagerRequest': null, 'banner': null, 'custom': null, + 'native': null, }) ]); @@ -92,6 +93,7 @@ void main() { 'adManagerRequest': request, 'banner': null, 'custom': null, + 'native': null, }) ]); @@ -118,6 +120,7 @@ void main() { 'adManagerRequest': null, 'banner': banner, 'custom': null, + 'native': null, }) ]); @@ -144,6 +147,34 @@ void main() { 'adManagerRequest': null, 'banner': null, 'custom': custom, + 'native': null, + }) + ]); + + expect(instanceManager.adFor(0), isNotNull); + }); + + test('load with $NativeParameters', () async { + final NativeParameters native = NativeParameters( + factoryId: 'test-factory-id', + ); + final AdLoaderAd adLoaderAd = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener(), + request: AdRequest(), + native: native, + ); + + await adLoaderAd.load(); + expect(log, [ + isMethodCall('loadAdLoaderAd', arguments: { + 'adId': 0, + 'adUnitId': 'test-ad-unit', + 'request': adLoaderAd.request, + 'adManagerRequest': null, + 'banner': null, + 'custom': null, + 'native': native, }) ]); diff --git a/packages/google_mobile_ads/test/mobile_ads_test.dart b/packages/google_mobile_ads/test/mobile_ads_test.dart index 279fbb695..aff04bb9b 100644 --- a/packages/google_mobile_ads/test/mobile_ads_test.dart +++ b/packages/google_mobile_ads/test/mobile_ads_test.dart @@ -607,5 +607,38 @@ void main() { }); } }); + + test('encode/decode minimal $NativeParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage(NativeParameters( + factoryId: 'test-factory-id', + ))!; + + NativeParameters result = codec.decodeMessage(byteData); + expect(result.factoryId, 'test-factory-id'); + expect(result.nativeAdOptions, null); + expect(result.viewOptions, null); + } + }); + + test('encode/decode $NativeParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage(NativeParameters( + factoryId: 'test-factory-id', + nativeAdOptions: NativeAdOptions(), + viewOptions: { + 'key': 'value', + }))!; + + NativeParameters result = codec.decodeMessage(byteData); + expect(result.factoryId, 'test-factory-id'); + expect(result.nativeAdOptions, NativeAdOptions()); + expect(result.viewOptions, { + 'key': 'value', + }); + } + }); }); }