Skip to content

Commit

Permalink
Prevent init from invoking result.success() multiple times. (#298)
Browse files Browse the repository at this point in the history
Prevent init from invoking result.success() multiple times.
  • Loading branch information
jjliu15 committed Jul 22, 2021
1 parent 24bc895 commit fcbbd31
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 16 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.2+1

* Fixes [Issue #130](https://github.com/googleads/googleads-mobile-flutter/issues/130)

## 0.13.2

* Fixes a crash where [PlatformView.getView() returns null](https://github.com/googleads/googleads-mobile-flutter/issues/46)
Expand Down
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.2";
public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.13.2+1";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 android.content.Context;
import androidx.annotation.NonNull;
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;

/** A wrapper around static methods in {@link com.google.android.gms.ads.MobileAds}. */
public class FlutterMobileAdsWrapper {

public FlutterMobileAdsWrapper() {}

/** Initializes the sdk. */
public void initialize(
@NonNull Context context, @NonNull OnInitializationCompleteListener listener) {
MobileAds.initialize(context, listener);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,24 @@ private static <T> T requireNonNull(T obj) {
@Nullable private AdInstanceManager instanceManager;
@Nullable private ActivityPluginBinding activityBinding;
private final Map<String, NativeAdFactory> nativeAdFactories = new HashMap<>();

private final FlutterMobileAdsWrapper flutterMobileAds;
/**
* Public constructor for the plugin. Dependency initialization is handled in lifecycle methods
* below.
*/
public GoogleMobileAdsPlugin() {}
public GoogleMobileAdsPlugin() {
this.flutterMobileAds = new FlutterMobileAdsWrapper();
}

/** Constructor for testing. */
@VisibleForTesting
protected GoogleMobileAdsPlugin(
@Nullable FlutterPluginBinding pluginBinding, @Nullable AdInstanceManager instanceManager) {
@Nullable FlutterPluginBinding pluginBinding,
@Nullable AdInstanceManager instanceManager,
@NonNull FlutterMobileAdsWrapper flutterMobileAds) {
this.pluginBinding = pluginBinding;
this.instanceManager = instanceManager;
this.flutterMobileAds = flutterMobileAds;
}

/**
Expand Down Expand Up @@ -226,14 +231,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
break;

case "MobileAds#initialize":
MobileAds.initialize(
instanceManager.activity,
new OnInitializationCompleteListener() {
@Override
public void onInitializationComplete(InitializationStatus initializationStatus) {
result.success(new FlutterInitializationStatus(initializationStatus));
}
});
flutterMobileAds.initialize(
instanceManager.activity, new FlutterInitializationListener(result));
break;
case "MobileAds#updateRequestConfiguration":
RequestConfiguration.Builder builder = MobileAds.getRequestConfiguration().toBuilder();
Expand Down Expand Up @@ -394,4 +393,28 @@ public void onInitializationComplete(InitializationStatus initializationStatus)
result.notImplemented();
}
}

/** An {@link OnInitializationCompleteListener} that invokes result.success() at most once. */
private static final class FlutterInitializationListener
implements OnInitializationCompleteListener {

private final Result result;
private boolean isInitializationCompleted;

private FlutterInitializationListener(@NonNull final Result result) {
this.result = result;
isInitializationCompleted = false;
}

@Override
public void onInitializationComplete(@NonNull InitializationStatus initializationStatus) {
// Make sure not to invoke this more than once, since Dart will throw an exception if success
// is invoked more than once. See b/193418432.
if (isInitializationCompleted) {
return;
}
result.success(new FlutterInitializationStatus(initializationStatus));
isInitializationCompleted = true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
import static org.mockito.Mockito.verify;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdapterResponseInfo;
import com.google.android.gms.ads.ResponseInfo;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
import com.google.android.gms.ads.nativead.NativeAd;
import com.google.android.gms.ads.nativead.NativeAdView;
import io.flutter.plugin.common.BinaryMessenger;
Expand Down Expand Up @@ -499,7 +502,8 @@ public void internalInitDisposesAds() {

// Check that ads are removed and disposed when "_init" is called.
AdInstanceManager testManagerSpy = spy(testManager);
GoogleMobileAdsPlugin plugin = new GoogleMobileAdsPlugin(null, testManagerSpy);
GoogleMobileAdsPlugin plugin =
new GoogleMobileAdsPlugin(null, testManagerSpy, new FlutterMobileAdsWrapper());
Result result = mock(Result.class);
MethodCall methodCall = new MethodCall("_init", null);
plugin.onMethodCall(methodCall, result);
Expand All @@ -514,6 +518,35 @@ public void internalInitDisposesAds() {
assertNull(testManager.adIdFor(banner));
}

@Test
public void initializeCallbackInvokedTwice() {
AdInstanceManager testManagerSpy = spy(testManager);
FlutterMobileAdsWrapper mockMobileAds = mock(FlutterMobileAdsWrapper.class);
GoogleMobileAdsPlugin plugin = new GoogleMobileAdsPlugin(null, testManagerSpy, mockMobileAds);
final InitializationStatus mockInitStatus = mock(InitializationStatus.class);
doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
// Invoke init listener twice.
OnInitializationCompleteListener listener = invocation.getArgument(1);
listener.onInitializationComplete(mockInitStatus);
listener.onInitializationComplete(mockInitStatus);
return null;
}
})
.when(mockMobileAds)
.initialize(
ArgumentMatchers.any(Context.class),
ArgumentMatchers.any(OnInitializationCompleteListener.class));

MethodCall methodCall = new MethodCall("MobileAds#initialize", null);
Result result = mock(Result.class);
plugin.onMethodCall(methodCall, result);

verify(result).success(ArgumentMatchers.any(FlutterInitializationStatus.class));
}

@Test(expected = IllegalArgumentException.class)
public void trackAdThrowsErrorForDuplicateId() {
final FlutterBannerAd banner = mock(FlutterBannerAd.class);
Expand Down
2 changes: 1 addition & 1 deletion packages/google_mobile_ads/ios/Classes/FLTConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
// limitations under the License.

/** Versioned request agent string. */
#define FLT_REQUEST_AGENT_VERSIONED @"Flutter-GMA-0.13.2"
#define FLT_REQUEST_AGENT_VERSIONED @"Flutter-GMA-0.13.2+1"
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,36 @@ @interface FLTGoogleMobileAdsPlugin ()
@property NSMutableDictionary<NSString *, id<FLTNativeAdFactory>> *nativeAdFactories;
@end

/// Initialization handler for GMASDK. Invokes result at most once.
@interface FLTInitializationHandler : NSObject
- (instancetype)initWithResult:(FlutterResult)result;
- (void)handleInitializationComplete:(GADInitializationStatus *_Nonnull)status;
@end

@implementation FLTInitializationHandler {
FlutterResult _result;
BOOL _isInitializationCompleted;
}

- (instancetype)initWithResult:(FlutterResult)result {
self = [super init];
if (self) {
_isInitializationCompleted = false;
_result = result;
}
return self;
}

- (void)handleInitializationComplete:(GADInitializationStatus *_Nonnull)status {
if (_isInitializationCompleted) {
return;
}
_result([[FLTInitializationStatus alloc] initWithStatus:status]);
_isInitializationCompleted = true;
}

@end

@implementation FLTGoogleMobileAdsPlugin {
NSMutableDictionary<NSString *, id<FLTNativeAdFactory>> *_nativeAdFactories;
FLTAdInstanceManager *_manager;
Expand Down Expand Up @@ -97,9 +127,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
UIApplication.sharedApplication.delegate.window.rootViewController;

if ([call.method isEqualToString:@"MobileAds#initialize"]) {
FLTInitializationHandler *handler = [[FLTInitializationHandler alloc] initWithResult:result];
[[GADMobileAds sharedInstance]
startWithCompletionHandler:^(GADInitializationStatus *_Nonnull status) {
result([[FLTInitializationStatus alloc] initWithStatus:status]);
[handler handleInitializationComplete:status];
}];
} else if ([call.method isEqualToString:@"_init"]) {
[_manager disposeAllAds];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,35 @@ - (void)testInternalInit {
OCMVerify([_mockAdInstanceManager disposeAllAds]);
}

- (void)testMobileAdsInitialize {
id gadMobileAdsClassMock = OCMClassMock([GADMobileAds class]);
OCMStub(ClassMethod([gadMobileAdsClassMock sharedInstance]))
.andReturn((GADMobileAds *)gadMobileAdsClassMock);
GADInitializationStatus *mockInitStatus = OCMClassMock([GADInitializationStatus class]);
OCMStub([mockInitStatus adapterStatusesByClassName]).andReturn(@{});
OCMStub([gadMobileAdsClassMock startWithCompletionHandler:[OCMArg any]])
.andDo(^(NSInvocation *invocation) {
// Invoke the init handler twice.
GADInitializationCompletionHandler completionHandler;
[invocation getArgument:&completionHandler atIndex:2];
completionHandler(mockInitStatus);
completionHandler(mockInitStatus);
});

FlutterMethodCall *methodCall =
[FlutterMethodCall methodCallWithMethodName:@"MobileAds#initialize" arguments:@{}];
__block int resultInvokedCount = 0;
__block id _Nullable returnedResult;
FlutterResult result = ^(id _Nullable result) {
resultInvokedCount += 1;
returnedResult = result;
};

[_fltGoogleMobileAdsPlugin handleMethodCall:methodCall result:result];
XCTAssertEqual(resultInvokedCount, 1);
XCTAssertEqual([((FLTInitializationStatus *)returnedResult) adapterStatuses].count, 0);
}

- (void)testSetSameAppKeyEnabledYes {
id gadMobileAdsClassMock = OCMClassMock([GADMobileAds class]);
OCMStub(ClassMethod([gadMobileAdsClassMock sharedInstance]))
Expand Down
2 changes: 1 addition & 1 deletion packages/google_mobile_ads/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ name: google_mobile_ads
description: Flutter plugin for Google Mobile Ads, supporting
banner, interstitial (full-screen), rewarded and native ads
homepage: https://github.com/googleads/googleads-mobile-flutter/tree/master/packages/google_mobile_ads
version: 0.13.2
version: 0.13.2+1

flutter:
plugin:
Expand Down

0 comments on commit fcbbd31

Please sign in to comment.