Skip to content

Commit 117982b

Browse files
authored
Rewarded server side verification options (#142)
* Add ServerSideVerificationOptions to RewardedAds
1 parent 99ab94d commit 117982b

22 files changed

+395
-38
lines changed

packages/google_mobile_ads/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.12.1
2+
3+
* Rewarded ads now take an optional `ServerSideVerification`, to support [custom data in rewarded ads](https://developers.google.com/admob/ios/rewarded-video-ssv#custom_data).
4+
15
## 0.12.0
26

37
* Migrated to null safety. Minimum Dart SDK version is bumped to 2.12.0.

packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ final class AdMessageCodec extends StandardMessageCodec {
3535
private static final byte VALUE_INITIALIZATION_STATE = (byte) 135;
3636
private static final byte VALUE_ADAPTER_STATUS = (byte) 136;
3737
private static final byte VALUE_INITIALIZATION_STATUS = (byte) 137;
38+
private static final byte VALUE_SERVER_SIDE_VERIFICATION_OPTIONS = (byte) 138;
3839

3940
@Override
4041
protected void writeValue(ByteArrayOutputStream stream, Object value) {
@@ -93,6 +94,11 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) {
9394
stream.write(VALUE_INITIALIZATION_STATUS);
9495
final FlutterInitializationStatus status = (FlutterInitializationStatus) value;
9596
writeValue(stream, status.adapterStatuses);
97+
} else if (value instanceof FlutterServerSideVerificationOptions) {
98+
stream.write(VALUE_SERVER_SIDE_VERIFICATION_OPTIONS);
99+
FlutterServerSideVerificationOptions options = (FlutterServerSideVerificationOptions) value;
100+
writeValue(stream, options.getUserId());
101+
writeValue(stream, options.getCustomData());
96102
} else {
97103
super.writeValue(stream, value);
98104
}
@@ -150,6 +156,10 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) {
150156
case VALUE_INITIALIZATION_STATUS:
151157
return new FlutterInitializationStatus(
152158
(Map<String, FlutterAdapterStatus>) readValueOfType(buffer.get(), buffer));
159+
case VALUE_SERVER_SIDE_VERIFICATION_OPTIONS:
160+
return new FlutterServerSideVerificationOptions(
161+
(String) readValueOfType(buffer.get(), buffer),
162+
(String) readValueOfType(buffer.get(), buffer));
153163
default:
154164
return super.readValueOfType(type, buffer);
155165
}

packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
/** Constants used in the plugin. */
1818
public class Constants {
1919
/** Version request agent. Should be bumped alongside plugin versions. */
20-
public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.12.0";
20+
public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.12.1";
2121
}

packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterRewardedAd.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class FlutterRewardedAd extends FlutterAd.FlutterOverlayAd {
3131
@NonNull private final String adUnitId;
3232
@Nullable private final FlutterAdRequest request;
3333
@Nullable private final FlutterPublisherAdRequest publisherRequest;
34+
@Nullable private final FlutterServerSideVerificationOptions serverSideVerificationOptions;
3435
@Nullable RewardedAd rewardedAd;
3536

3637
static class FlutterRewardItem {
@@ -68,26 +69,34 @@ public int hashCode() {
6869
public FlutterRewardedAd(
6970
@NonNull AdInstanceManager manager,
7071
@NonNull String adUnitId,
71-
@NonNull FlutterAdRequest request) {
72+
@NonNull FlutterAdRequest request,
73+
@Nullable FlutterServerSideVerificationOptions serverSideVerificationOptions) {
7274
this.manager = manager;
7375
this.adUnitId = adUnitId;
7476
this.request = request;
7577
this.publisherRequest = null;
78+
this.serverSideVerificationOptions = serverSideVerificationOptions;
7679
}
7780

7881
public FlutterRewardedAd(
7982
@NonNull AdInstanceManager manager,
8083
@NonNull String adUnitId,
81-
@NonNull FlutterPublisherAdRequest publisherRequest) {
84+
@NonNull FlutterPublisherAdRequest publisherRequest,
85+
@Nullable FlutterServerSideVerificationOptions serverSideVerificationOptions) {
8286
this.manager = manager;
8387
this.adUnitId = adUnitId;
8488
this.publisherRequest = publisherRequest;
8589
this.request = null;
90+
this.serverSideVerificationOptions = serverSideVerificationOptions;
8691
}
8792

8893
@Override
8994
void load() {
9095
rewardedAd = createRewardedAd();
96+
if (serverSideVerificationOptions != null) {
97+
rewardedAd.setServerSideVerificationOptions(
98+
serverSideVerificationOptions.asServerSideVerificationOptions());
99+
}
91100
final RewardedAdLoadCallback adLoadCallback =
92101
new RewardedAdLoadCallback() {
93102
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.flutter.plugins.googlemobileads;
2+
3+
import androidx.annotation.Nullable;
4+
import com.google.android.gms.ads.rewarded.ServerSideVerificationOptions;
5+
import java.util.Objects;
6+
7+
/** A wrapper for {@link ServerSideVerificationOptions}. */
8+
class FlutterServerSideVerificationOptions {
9+
10+
@Nullable private final String userId;
11+
@Nullable private final String customData;
12+
13+
public FlutterServerSideVerificationOptions(
14+
@Nullable String userId, @Nullable String customData) {
15+
this.userId = userId;
16+
this.customData = customData;
17+
}
18+
19+
@Nullable
20+
public String getUserId() {
21+
return userId;
22+
}
23+
24+
@Nullable
25+
public String getCustomData() {
26+
return customData;
27+
}
28+
29+
/** Gets an equivalent {@link ServerSideVerificationOptions}. */
30+
public ServerSideVerificationOptions asServerSideVerificationOptions() {
31+
ServerSideVerificationOptions.Builder builder = new ServerSideVerificationOptions.Builder();
32+
if (userId != null) {
33+
builder.setUserId(userId);
34+
}
35+
if (customData != null) {
36+
builder.setCustomData(customData);
37+
}
38+
return builder.build();
39+
}
40+
41+
@Override
42+
public boolean equals(@Nullable Object obj) {
43+
if (this == obj) {
44+
return true;
45+
}
46+
if (!(obj instanceof FlutterServerSideVerificationOptions)) {
47+
return false;
48+
}
49+
FlutterServerSideVerificationOptions other = (FlutterServerSideVerificationOptions) obj;
50+
return Objects.equals(other.userId, userId) && Objects.equals(other.customData, customData);
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hash(userId, customData);
56+
}
57+
}

packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,24 @@ public void onInitializationComplete(InitializationStatus initializationStatus)
303303
final String adUnitId = requireNonNull(call.<String>argument("adUnitId"));
304304
final FlutterAdRequest request = call.argument("request");
305305
final FlutterPublisherAdRequest publisherRequest = call.argument("publisherRequest");
306+
final FlutterServerSideVerificationOptions serverSideVerificationOptions =
307+
call.argument("serverSideVerificationOptions");
306308

307309
final FlutterRewardedAd rewardedAd;
308310
if (request != null) {
309-
rewardedAd = new FlutterRewardedAd(requireNonNull(instanceManager), adUnitId, request);
311+
rewardedAd =
312+
new FlutterRewardedAd(
313+
requireNonNull(instanceManager),
314+
adUnitId,
315+
request,
316+
serverSideVerificationOptions);
310317
} else if (publisherRequest != null) {
311318
rewardedAd =
312-
new FlutterRewardedAd(requireNonNull(instanceManager), adUnitId, publisherRequest);
319+
new FlutterRewardedAd(
320+
requireNonNull(instanceManager),
321+
adUnitId,
322+
publisherRequest,
323+
serverSideVerificationOptions);
313324
} else {
314325
result.error("InvalidRequest", "A null or invalid ad request was provided.", null);
315326
break;

packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,42 @@ public void adMessageCodec_decodeInitializationStatus() {
103103
(FlutterPublisherAdRequest) codec.decodeMessage((ByteBuffer) message.position(0));
104104
assertEquals(decodedPublisherAdRequest, flutterPublisherAdRequest);
105105
}
106+
107+
@Test
108+
public void adMessageCodec_decodeServerSideVerificationOptions() {
109+
AdMessageCodec codec = new AdMessageCodec();
110+
FlutterServerSideVerificationOptions options =
111+
new FlutterServerSideVerificationOptions("user-id", "custom-data");
112+
113+
ByteBuffer message = codec.encodeMessage(options);
114+
115+
FlutterServerSideVerificationOptions decodedOptions =
116+
(FlutterServerSideVerificationOptions)
117+
codec.decodeMessage((ByteBuffer) message.position(0));
118+
assertEquals(decodedOptions, options);
119+
120+
// With userId = null.
121+
options = new FlutterServerSideVerificationOptions(null, "custom-data");
122+
message = codec.encodeMessage(options);
123+
decodedOptions =
124+
(FlutterServerSideVerificationOptions)
125+
codec.decodeMessage((ByteBuffer) message.position(0));
126+
assertEquals(decodedOptions, options);
127+
128+
// With customData = null.
129+
options = new FlutterServerSideVerificationOptions("user-Id", null);
130+
message = codec.encodeMessage(options);
131+
decodedOptions =
132+
(FlutterServerSideVerificationOptions)
133+
codec.decodeMessage((ByteBuffer) message.position(0));
134+
assertEquals(decodedOptions, options);
135+
136+
// With userId and customData = null.
137+
options = new FlutterServerSideVerificationOptions(null, null);
138+
message = codec.encodeMessage(options);
139+
decodedOptions =
140+
(FlutterServerSideVerificationOptions)
141+
codec.decodeMessage((ByteBuffer) message.position(0));
142+
assertEquals(decodedOptions, options);
143+
}
106144
}

packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsTest.java

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.android.gms.ads.rewarded.RewardedAd;
3535
import com.google.android.gms.ads.rewarded.RewardedAdCallback;
3636
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback;
37+
import com.google.android.gms.ads.rewarded.ServerSideVerificationOptions;
3738
import io.flutter.plugin.common.BinaryMessenger;
3839
import io.flutter.plugin.common.MethodCall;
3940
import io.flutter.plugin.common.MethodChannel.Result;
@@ -46,6 +47,7 @@
4647
import org.junit.Before;
4748
import org.junit.Test;
4849
import org.mockito.ArgumentCaptor;
50+
import org.mockito.ArgumentMatcher;
4951
import org.mockito.ArgumentMatchers;
5052
import org.mockito.Mockito;
5153

@@ -198,8 +200,10 @@ public void loadRewardedAdWithPublisherRequest() {
198200
final PublisherAdRequest mockRequest = mock(PublisherAdRequest.class);
199201
when(mockFlutterRequest.asPublisherAdRequest()).thenReturn(mockRequest);
200202

203+
final FlutterServerSideVerificationOptions options =
204+
new FlutterServerSideVerificationOptions("userId", "customData");
201205
final FlutterRewardedAd rewardedAd =
202-
new FlutterRewardedAd(testManager, "testId", mockFlutterRequest);
206+
new FlutterRewardedAd(testManager, "testId", mockFlutterRequest, options);
203207

204208
final FlutterRewardedAd mockFlutterAd = spy(rewardedAd);
205209
final RewardedAd mockPublisherAd = mock(RewardedAd.class);
@@ -210,6 +214,50 @@ public void loadRewardedAdWithPublisherRequest() {
210214
ArgumentCaptor.forClass(PublisherAdRequest.class);
211215
verify(mockPublisherAd)
212216
.loadAd(captor.capture(), ArgumentMatchers.any(RewardedAdLoadCallback.class));
217+
ArgumentMatcher<ServerSideVerificationOptions> serverSideVerificationOptionsArgumentMatcher =
218+
new ArgumentMatcher<ServerSideVerificationOptions>() {
219+
@Override
220+
public boolean matches(ServerSideVerificationOptions argument) {
221+
return argument.getCustomData().equals(options.getCustomData())
222+
&& argument.getUserId().equals(options.getUserId());
223+
}
224+
};
225+
verify(mockPublisherAd)
226+
.setServerSideVerificationOptions(
227+
ArgumentMatchers.argThat(serverSideVerificationOptionsArgumentMatcher));
228+
assertEquals(captor.getValue(), mockRequest);
229+
}
230+
231+
@Test
232+
public void loadRewardedAdWithPublisherRequest_nullServerSideOptions() {
233+
final FlutterPublisherAdRequest mockFlutterRequest = mock(FlutterPublisherAdRequest.class);
234+
final PublisherAdRequest mockRequest = mock(PublisherAdRequest.class);
235+
when(mockFlutterRequest.asPublisherAdRequest()).thenReturn(mockRequest);
236+
237+
final FlutterServerSideVerificationOptions options =
238+
new FlutterServerSideVerificationOptions(null, null);
239+
final FlutterRewardedAd rewardedAd =
240+
new FlutterRewardedAd(testManager, "testId", mockFlutterRequest, options);
241+
242+
final FlutterRewardedAd mockFlutterAd = spy(rewardedAd);
243+
final RewardedAd mockPublisherAd = mock(RewardedAd.class);
244+
doReturn(mockPublisherAd).when(mockFlutterAd).createRewardedAd();
245+
mockFlutterAd.load();
246+
247+
final ArgumentCaptor<PublisherAdRequest> captor =
248+
ArgumentCaptor.forClass(PublisherAdRequest.class);
249+
verify(mockPublisherAd)
250+
.loadAd(captor.capture(), ArgumentMatchers.any(RewardedAdLoadCallback.class));
251+
ArgumentMatcher<ServerSideVerificationOptions> serverSideVerificationOptionsArgumentMatcher =
252+
new ArgumentMatcher<ServerSideVerificationOptions>() {
253+
@Override
254+
public boolean matches(ServerSideVerificationOptions argument) {
255+
return argument.getCustomData().isEmpty() && argument.getUserId().isEmpty();
256+
}
257+
};
258+
verify(mockPublisherAd)
259+
.setServerSideVerificationOptions(
260+
ArgumentMatchers.argThat(serverSideVerificationOptionsArgumentMatcher));
213261
assertEquals(captor.getValue(), mockRequest);
214262
}
215263

@@ -218,7 +266,7 @@ public void showRewardedAd() {
218266
final FlutterAdRequest mockFlutterRequest = mock(FlutterAdRequest.class);
219267

220268
final FlutterRewardedAd rewardedAd =
221-
new FlutterRewardedAd(testManager, "testId", mockFlutterRequest);
269+
new FlutterRewardedAd(testManager, "testId", mockFlutterRequest, null);
222270

223271
final FlutterRewardedAd mockFlutterAd = spy(rewardedAd);
224272
final RewardedAd mockRewardedAd = mock(RewardedAd.class);
@@ -535,7 +583,7 @@ public void flutterAdListener_onAdClosed() {
535583

536584
@Test
537585
public void flutterAdListener_onRewardedAdUserEarnedReward() {
538-
final FlutterRewardedAd ad = new FlutterRewardedAd(testManager, "testId", request);
586+
final FlutterRewardedAd ad = new FlutterRewardedAd(testManager, "testId", request, null);
539587
testManager.trackAd(ad, 0);
540588

541589
testManager.onRewardedAdUserEarnedReward(

packages/google_mobile_ads/ios/Classes/FLTAd_Internal.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#import <GoogleMobileAds/GoogleMobileAds.h>
1616
#import "FLTAdInstanceManager_Internal.h"
1717
#import "FLTGoogleMobileAdsPlugin.h"
18+
#import "FLTMobileAds_Internal.h"
1819

1920
@class FLTAdInstanceManager;
2021
@protocol FLTNativeAdFactory;
@@ -99,7 +100,9 @@
99100
@property(weak) FLTAdInstanceManager *_Nullable manager;
100101
- (instancetype _Nonnull)initWithAdUnitId:(NSString *_Nonnull)adUnitId
101102
request:(FLTAdRequest *_Nonnull)request
102-
rootViewController:(UIViewController *_Nonnull)rootViewController;
103+
rootViewController:(UIViewController *_Nonnull)rootViewController
104+
serverSideVerificationOptions:
105+
(FLTServerSideVerificationOptions *_Nullable)serverSideVerificationOptions;
103106
- (GADRewardedAd *_Nonnull)rewardedAd;
104107
@end
105108

packages/google_mobile_ads/ios/Classes/FLTAd_Internal.m

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,16 +309,20 @@ @implementation FLTRewardedAd {
309309
GADRewardedAd *_rewardedView;
310310
FLTAdRequest *_adRequest;
311311
UIViewController *_rootViewController;
312+
FLTServerSideVerificationOptions *_serverSideVerificationOptions;
312313
}
313314

314315
- (instancetype)initWithAdUnitId:(NSString *_Nonnull)adUnitId
315-
request:(FLTAdRequest *_Nonnull)request
316-
rootViewController:(UIViewController *_Nonnull)rootViewController {
316+
request:(FLTAdRequest *_Nonnull)request
317+
rootViewController:(UIViewController *_Nonnull)rootViewController
318+
serverSideVerificationOptions:
319+
(FLTServerSideVerificationOptions *_Nullable)serverSideVerificationOptions {
317320
self = [super init];
318321
if (self) {
319322
_adRequest = request;
320323
_rewardedView = [[GADRewardedAd alloc] initWithAdUnitID:adUnitId];
321324
_rootViewController = rootViewController;
325+
_serverSideVerificationOptions = serverSideVerificationOptions;
322326
}
323327
return self;
324328
}
@@ -338,6 +342,11 @@ - (void)load {
338342
NSLog(@"A null or invalid ad request was provided.");
339343
return;
340344
}
345+
if (_serverSideVerificationOptions != NULL &&
346+
![_serverSideVerificationOptions isEqual:[NSNull null]]) {
347+
_rewardedView.serverSideVerificationOptions =
348+
[_serverSideVerificationOptions asGADServerSideVerificationOptions];
349+
}
341350

342351
[self.rewardedAd loadRequest:request
343352
completionHandler:^(GADRequestError *_Nullable error) {

packages/google_mobile_ads/ios/Classes/FLTConstants.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
// limitations under the License.
1414

1515
/** Versioned request agent string. */
16-
#define FLT_REQUEST_AGENT_VERSIONED @"Flutter-GMA-0.12.0"
16+
#define FLT_REQUEST_AGENT_VERSIONED @"Flutter-GMA-0.12.1"

packages/google_mobile_ads/ios/Classes/FLTGoogleMobileAdsPlugin.m

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
217217
return;
218218
}
219219

220-
FLTRewardedAd *ad = [[FLTRewardedAd alloc] initWithAdUnitId:call.arguments[@"adUnitId"]
221-
request:request
222-
rootViewController:rootController];
220+
FLTRewardedAd *ad =
221+
[[FLTRewardedAd alloc] initWithAdUnitId:call.arguments[@"adUnitId"]
222+
request:request
223+
rootViewController:rootController
224+
serverSideVerificationOptions:call.arguments[@"serverSideVerificationOptions"]];
223225
[_manager loadAd:ad adId:call.arguments[@"adId"]];
224226
result(nil);
225227
} else if ([call.method isEqualToString:@"disposeAd"]) {

0 commit comments

Comments
 (0)