Skip to content

Commit

Permalink
Add Paid Events (#243)
Browse files Browse the repository at this point in the history
* Add support for paid events callback

* Invoke all android method calls on ui thread.

Co-authored-by: Benjamin Koch <[email protected]>
  • Loading branch information
jjliu15 and kolotum authored Jun 18, 2021
1 parent ffb49da commit baf19aa
Show file tree
Hide file tree
Showing 33 changed files with 771 additions and 33 deletions.
4 changes: 4 additions & 0 deletions packages/google_mobile_ads/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.13.1

* Adds support for the paid event callback.

## 0.13.0

* Updates GMA Android and iOS dependencies to 20.1.0 and 8.5.0, respectively.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,15 @@ void onAdLoaded(@NonNull FlutterAd ad, @Nullable ResponseInfo responseInfo) {
FlutterResponseInfo flutterResponseInfo =
(responseInfo == null) ? null : new FlutterResponseInfo(responseInfo);
arguments.put("responseInfo", flutterResponseInfo);
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdFailedToLoad(@NonNull FlutterAd ad, @NonNull FlutterAd.FlutterLoadAdError error) {
Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdFailedToLoad");
arguments.put("loadAdError", error);
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAppEvent(@NonNull FlutterAd ad, @NonNull String name, @NonNull String data) {
Expand All @@ -120,35 +120,35 @@ void onAppEvent(@NonNull FlutterAd ad, @NonNull String name, @NonNull String dat
arguments.put("eventName", "onAppEvent");
arguments.put("name", name);
arguments.put("data", data);
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdImpression(@NonNull FlutterAd ad) {
Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdImpression");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onNativeAdClicked(@NonNull FlutterNativeAd ad) {
Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onNativeAdClicked");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdOpened(@NonNull FlutterAd ad) {
Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdOpened");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdClosed(@NonNull FlutterAd ad) {
Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdClosed");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onRewardedAdUserEarnedReward(
Expand All @@ -157,36 +157,46 @@ void onRewardedAdUserEarnedReward(
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onRewardedAdUserEarnedReward");
arguments.put("rewardItem", reward);
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onPaidEvent(@NonNull FlutterAd ad, @NonNull FlutterAdValue adValue) {
final Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onPaidEvent");
arguments.put("valueMicros", adValue.valueMicros);
arguments.put("precision", adValue.precisionType);
arguments.put("currencyCode", adValue.currencyCode);
invokeOnAdEvent(arguments);
}

void onFailedToShowFullScreenContent(@NonNull FlutterAd ad, @NonNull AdError error) {
final Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onFailedToShowFullScreenContent");
arguments.put("error", new FlutterAdError(error));
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdShowedFullScreenContent(@NonNull FlutterAd ad) {
final Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdShowedFullScreenContent");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdDismissedFullScreenContent(@NonNull FlutterAd ad) {
final Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdDismissedFullScreenContent");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

void onAdMetadataChanged(@NonNull FlutterAd ad) {
final Map<Object, Object> arguments = new HashMap<>();
arguments.put("adId", adIdFor(ad));
arguments.put("eventName", "onAdMetadataChanged");
channel.invokeMethod("onAdEvent", arguments);
invokeOnAdEvent(arguments);
}

boolean showAdWithId(int id) {
Expand All @@ -199,4 +209,15 @@ boolean showAdWithId(int id) {
ad.show();
return true;
}

/** Invoke the method channel using the UI thread. Otherwise the message gets silently dropped. */
private void invokeOnAdEvent(final Map<Object, Object> arguments) {
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
channel.invokeMethod("onAdEvent", arguments);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
/** Constants used in the plugin. */
public class Constants {
/** Version request agent. Should be bumped alongside plugin versions. */
public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.13.0";
public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.13.1";
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ void load() {
@Override
public void onAdLoaded(@NonNull AdManagerInterstitialAd adManagerInterstitialAd) {
FlutterAdManagerInterstitialAd.this.ad = adManagerInterstitialAd;
ad.setOnPaidEventListener(
new FlutterPaidEventListener(manager, FlutterAdManagerInterstitialAd.this));
ad.setAppEventListener(
new AppEventListener() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2021 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;

/** A wrapper for {@link com.google.android.gms.ads.AdValue}. */
public class FlutterAdValue {
final int precisionType;
@NonNull final String currencyCode;
final long valueMicros;

public FlutterAdValue(int precisionType, @NonNull String currencyCode, long valueMicros) {
this.precisionType = precisionType;
this.currencyCode = currencyCode;
this.valueMicros = valueMicros;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ void load() {
view = bannerAdCreator.createAdView();
view.setAdUnitId(adUnitId);
view.setAdSize(size.getAdSize());
view.setOnPaidEventListener(new FlutterPaidEventListener(manager, this));
view.setAdListener(
new FlutterBannerAdListener(
manager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ void load() {
@Override
public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
FlutterInterstitialAd.this.ad = interstitialAd;
interstitialAd.setOnPaidEventListener(
new FlutterPaidEventListener(manager, FlutterInterstitialAd.this));
FlutterInterstitialAd.this.manager.onAdLoaded(
FlutterInterstitialAd.this, interstitialAd.getResponseInfo());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ void load() {
public void onNativeAdLoaded(@NonNull NativeAd nativeAd) {
ad = adFactory.createNativeAd(nativeAd, customOptions);
responseInfo = nativeAd.getResponseInfo();
nativeAd.setOnPaidEventListener(
new FlutterPaidEventListener(manager, FlutterNativeAd.this));
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2021 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 com.google.android.gms.ads.AdValue;
import com.google.android.gms.ads.OnPaidEventListener;

/** Implementation of {@link OnPaidEventListener} that sends events to {@link AdInstanceManager}. */
public class FlutterPaidEventListener implements OnPaidEventListener {
@NonNull private final AdInstanceManager manager;
@NonNull private final FlutterAd ad;

FlutterPaidEventListener(@NonNull AdInstanceManager manager, @NonNull FlutterAd ad) {
this.manager = manager;
this.ad = ad;
}

@Override
public void onPaidEvent(AdValue adValue) {
FlutterAdValue value =
new FlutterAdValue(
adValue.getPrecisionType(), adValue.getCurrencyCode(), adValue.getValueMicros());
manager.onPaidEvent(ad, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public void onAdLoaded(@NonNull RewardedAd rewardedAd) {
serverSideVerificationOptions.asServerSideVerificationOptions());
}
manager.onAdLoaded(FlutterRewardedAd.this, rewardedAd.getResponseInfo());
rewardedAd.setOnPaidEventListener(
new FlutterPaidEventListener(manager, FlutterRewardedAd.this));

super.onAdLoaded(rewardedAd);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package io.flutter.plugins.googlemobileads;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
Expand All @@ -27,6 +29,7 @@
import android.app.Activity;
import android.content.Context;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdValue;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.ResponseInfo;
Expand All @@ -38,6 +41,7 @@
import io.flutter.plugins.googlemobileads.FlutterAd.FlutterLoadAdError;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

Expand Down Expand Up @@ -123,6 +127,22 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
final ResponseInfo responseInfo = mock(ResponseInfo.class);
doReturn(responseInfo).when(mockAdManagerAd).getResponseInfo();

final AdValue adValue = mock(AdValue.class);
doReturn(1).when(adValue).getPrecisionType();
doReturn("Dollars").when(adValue).getCurrencyCode();
doReturn(1000L).when(adValue).getValueMicros();
doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
FlutterPaidEventListener listener = invocation.getArgument(0);
listener.onPaidEvent(adValue);
return null;
}
})
.when(mockAdManagerAd)
.setOnPaidEventListener(any(FlutterPaidEventListener.class));

flutterAdManagerInterstitialAd.load();

verify(mockFlutterAdLoader)
Expand All @@ -132,8 +152,15 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
eq(mockRequest),
any(AdManagerInterstitialAdLoadCallback.class));

verify(mockAdManagerAd).setOnPaidEventListener(any(FlutterPaidEventListener.class));
verify(mockManager).onAdLoaded(flutterAdManagerInterstitialAd, responseInfo);
final ArgumentCaptor<FlutterAdValue> adValueCaptor = forClass(FlutterAdValue.class);
verify(mockManager).onPaidEvent(eq(flutterAdManagerInterstitialAd), adValueCaptor.capture());
assertEquals(adValueCaptor.getValue().currencyCode, "Dollars");
assertEquals(adValueCaptor.getValue().precisionType, 1);
assertEquals(adValueCaptor.getValue().valueMicros, 1000L);

// Setup mocks for show().
doAnswer(
new Answer() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
Expand All @@ -29,13 +30,15 @@
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.AdView;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.ResponseInfo;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.googlemobileads.FlutterAd.FlutterLoadAdError;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

Expand Down Expand Up @@ -115,15 +118,38 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
final ResponseInfo responseInfo = mock(ResponseInfo.class);
doReturn(responseInfo).when(mockAdView).getResponseInfo();

final AdValue adValue = mock(AdValue.class);
doReturn(1).when(adValue).getPrecisionType();
doReturn("Dollars").when(adValue).getCurrencyCode();
doReturn(1000L).when(adValue).getValueMicros();
doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
FlutterPaidEventListener listener = invocation.getArgument(0);
listener.onPaidEvent(adValue);
return null;
}
})
.when(mockAdView)
.setOnPaidEventListener(any(FlutterPaidEventListener.class));

flutterBannerAd.load();

verify(mockAdView).loadAd(eq(mockAdRequest));
verify(mockAdView).setAdListener(any(AdListener.class));
verify(mockAdView).setAdUnitId(eq("testId"));
verify(mockAdView).setAdSize(adSize);
verify(mockAdView).setOnPaidEventListener(any(FlutterPaidEventListener.class));
verify(mockManager).onAdLoaded(eq(flutterBannerAd), eq(responseInfo));
verify(mockManager).onAdImpression(eq(flutterBannerAd));
verify(mockManager).onAdClosed(eq(flutterBannerAd));
verify(mockManager).onAdOpened(eq(flutterBannerAd));
final ArgumentCaptor<FlutterAdValue> adValueCaptor = forClass(FlutterAdValue.class);
verify(mockManager).onPaidEvent(eq(flutterBannerAd), adValueCaptor.capture());
assertEquals(adValueCaptor.getValue().currencyCode, "Dollars");
assertEquals(adValueCaptor.getValue().precisionType, 1);
assertEquals(adValueCaptor.getValue().valueMicros, 1000L);
assertEquals(flutterBannerAd.getView(), mockAdView);
}

Expand Down
Loading

0 comments on commit baf19aa

Please sign in to comment.