-
Notifications
You must be signed in to change notification settings - Fork 124
AdMob Rewarded Ads on iOS #752
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* 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 | ||
* | ||
* http://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. | ||
*/ | ||
|
||
// An Objective-C++ wrapper class that conforms to the | ||
// GADRewardedAdDelegate protocol. When the delegate for receiving state | ||
// change messages from a GADRewardedAd is notified, this wrapper class | ||
// forwards the notification to the RewardedAdInternalIOS object to handle | ||
// the state changes for an rewarded ad. | ||
|
||
#import <Foundation/Foundation.h> | ||
#import <GoogleMobileAds/GoogleMobileAds.h> | ||
|
||
namespace firebase { | ||
namespace admob { | ||
namespace internal { | ||
class RewardedAdInternalIOS; | ||
} // namespace internal | ||
} // namespace admob | ||
} // namespace firebase | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface FADRewardedAdDelegate : NSObject<GADFullScreenContentDelegate> | ||
|
||
/// Returns a FADInterstitialDelegate object with InterstitialAdInternalIOS. | ||
- (FADRewardedAdDelegate *)initWithInternalRewardedAd: | ||
(firebase::admob::internal::RewardedAdInternalIOS *)rewardedAd; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* 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 | ||
* | ||
* http://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. | ||
*/ | ||
|
||
#import "admob/src/ios/FADRewardedAdDelegate.h" | ||
|
||
#include "admob/src/ios/ad_result_ios.h" | ||
#include "admob/src/ios/rewarded_ad_internal_ios.h" | ||
|
||
@interface FADRewardedAdDelegate () { | ||
/// The RewardedAdInternalIOS object. | ||
firebase::admob::internal::RewardedAdInternalIOS *_rewardedAd; | ||
} | ||
@end | ||
|
||
@implementation FADRewardedAdDelegate : NSObject | ||
|
||
#pragma mark - Initialization | ||
|
||
- (instancetype)initWithInternalRewardedAd: | ||
(firebase::admob::internal::RewardedAdInternalIOS *)rewardedAd { | ||
self = [super init]; | ||
if (self) { | ||
_rewardedAd = rewardedAd; | ||
} | ||
|
||
return self; | ||
} | ||
|
||
#pragma mark - GADFullScreenContentDelegate | ||
|
||
// Capture AdMob iOS Full screen events and forward them to our C++ | ||
// translation layer. | ||
- (void)adDidRecordImpression:(nonnull id<GADFullScreenPresentingAd>)ad { | ||
_rewardedAd->NotifyListenerOfAdImpression(); | ||
} | ||
|
||
- (void)adDidRecordClick:(nonnull id<GADFullScreenPresentingAd>)ad { | ||
_rewardedAd->NotifyListenerOfAdClickedFullScreenContent(); | ||
} | ||
|
||
- (void)ad:(nonnull id<GADFullScreenPresentingAd>)ad | ||
didFailToPresentFullScreenContentWithError:(nonnull NSError *)error { | ||
firebase::admob::AdResultInternal ad_result_internal; | ||
ad_result_internal.is_wrapper_error = false; | ||
ad_result_internal.is_successful = false; | ||
ad_result_internal.ios_error = error; | ||
// Invoke AdMobInternal, a friend of AdResult, to have it access its | ||
// protected constructor with the AdError data. | ||
const firebase::admob::AdResult& ad_result = firebase::admob::AdMobInternal::CreateAdResult(ad_result_internal); | ||
_rewardedAd->NotifyListenerOfAdFailedToShowFullScreenContent(ad_result); | ||
} | ||
|
||
- (void)adDidPresentFullScreenContent:(nonnull id<GADFullScreenPresentingAd>)ad { | ||
_rewardedAd->NotifyListenerOfAdShowedFullScreenContent(); | ||
} | ||
|
||
- (void)adDidDismissFullScreenContent:(nonnull id<GADFullScreenPresentingAd>)ad { | ||
_rewardedAd->NotifyListenerOfAdDismissedFullScreenContent(); | ||
} | ||
|
||
@end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,23 +30,148 @@ | |
ad_load_callback_data_(nil), rewarded_ad_(nil), | ||
parent_view_(nil), rewarded_ad_delegate_(nil) {} | ||
|
||
RewardedAdInternalIOS::~RewardedAdInternalIOS() { } | ||
RewardedAdInternalIOS::~RewardedAdInternalIOS() { | ||
firebase::MutexLock lock(mutex_); | ||
// Clean up any resources created in RewardedAdInternalIOS. | ||
Mutex mutex(Mutex::kModeNonRecursive); | ||
__block Mutex *mutex_in_block = &mutex; | ||
mutex.Acquire(); | ||
void (^destroyBlock)() = ^{ | ||
((GADRewardedAd*)rewarded_ad_).fullScreenContentDelegate = nil; | ||
rewarded_ad_delegate_ = nil; | ||
rewarded_ad_ = nil; | ||
if(ad_load_callback_data_ != nil) { | ||
delete ad_load_callback_data_; | ||
ad_load_callback_data_ = nil; | ||
} | ||
mutex_in_block->Release(); | ||
}; | ||
util::DispatchAsyncSafeMainQueue(destroyBlock); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a pattern we've done before, I wonder if we should actually make a DispatchAsyncSafeMainQueueBlocking, and also add tests for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to change this in the next PR which includes some other stability fixes. |
||
mutex.Acquire(); | ||
mutex.Release(); | ||
} | ||
|
||
Future<void> RewardedAdInternalIOS::Initialize(AdParent parent) { | ||
firebase::MutexLock lock(mutex_); | ||
return CreateAndCompleteFuture( | ||
kRewardedAdFnInitialize, kAdMobErrorNone, nullptr, &future_data_); | ||
const SafeFutureHandle<void> future_handle = | ||
future_data_.future_impl.SafeAlloc<void>(kRewardedAdFnInitialize); | ||
|
||
if(initialized_) { | ||
CompleteFuture(kAdMobErrorAlreadyInitialized, | ||
kAdAlreadyInitializedErrorMessage, future_handle, &future_data_); | ||
} else { | ||
initialized_ = true; | ||
parent_view_ = (UIView *)parent; | ||
CompleteFuture(kAdMobErrorNone, nullptr, future_handle, &future_data_); | ||
} | ||
return MakeFuture(&future_data_.future_impl, future_handle); | ||
} | ||
|
||
Future<AdResult> RewardedAdInternalIOS::LoadAd( | ||
const char* ad_unit_id, const AdRequest& request) { | ||
return CreateAndCompleteFutureWithResult( | ||
kRewardedAdFnLoadAd, kAdMobErrorNone, nullptr, &future_data_, AdResult()); | ||
firebase::MutexLock lock(mutex_); | ||
FutureCallbackData<AdResult>* callback_data = | ||
CreateAdResultFutureCallbackData(kRewardedAdFnLoadAd, | ||
&future_data_); | ||
SafeFutureHandle<AdResult> future_handle = callback_data->future_handle; | ||
|
||
if (ad_load_callback_data_ != nil) { | ||
CompleteLoadAdInternalResult(callback_data, kAdMobErrorLoadInProgress, | ||
kAdLoadInProgressErrorMessage); | ||
return MakeFuture(&future_data_.future_impl, future_handle); | ||
} | ||
|
||
// Persist a pointer to the callback data so that we may use it after the iOS | ||
// SDK returns the AdResult. | ||
ad_load_callback_data_ = callback_data; | ||
|
||
rewarded_ad_delegate_ = | ||
[[FADRewardedAdDelegate alloc] initWithInternalRewardedAd:this]; | ||
|
||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
// Create a GADRequest from an admob::AdRequest. | ||
AdMobError error_code = kAdMobErrorNone; | ||
std::string error_message; | ||
GADRequest *ad_request = | ||
GADRequestFromCppAdRequest(request, &error_code, &error_message); | ||
if (ad_request == nullptr) { | ||
if (error_code == kAdMobErrorNone) { | ||
error_code = kAdMobErrorInternalError; | ||
error_message = kAdCouldNotParseAdRequestErrorMessage; | ||
} | ||
CompleteLoadAdInternalResult(ad_load_callback_data_, error_code, | ||
error_message.c_str()); | ||
ad_load_callback_data_ = nil; | ||
} else { | ||
// Make the rewarded ad request. | ||
[GADRewardedAd loadWithAdUnitID:@(ad_unit_id) | ||
request:ad_request | ||
completionHandler:^(GADRewardedAd *ad, NSError *error) // NO LINT | ||
{ | ||
if (error) { | ||
RewardedAdDidFailToReceiveAdWithError(error); | ||
} else { | ||
RewardedAdDidReceiveAd(ad); | ||
} | ||
}]; | ||
} | ||
}); | ||
|
||
return MakeFuture(&future_data_.future_impl, future_handle); | ||
} | ||
|
||
Future<void> RewardedAdInternalIOS::Show(UserEarnedRewardListener* listener) { | ||
return CreateAndCompleteFuture( | ||
kRewardedAdFnShow, kAdMobErrorNone, nullptr, &future_data_); | ||
firebase::MutexLock lock(mutex_); | ||
const firebase::SafeFutureHandle<void> handle = | ||
future_data_.future_impl.SafeAlloc<void>(kRewardedAdFnShow); | ||
user_earned_reward_listener_ = listener; | ||
|
||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
AdMobError error_code = kAdMobErrorLoadInProgress; | ||
const char* error_message = kAdLoadInProgressErrorMessage; | ||
if (rewarded_ad_ == nil) { | ||
error_code = kAdMobErrorUninitialized; | ||
error_message = kAdUninitializedErrorMessage; | ||
} else { | ||
[rewarded_ad_ | ||
presentFromRootViewController:[parent_view_ window].rootViewController | ||
userDidEarnRewardHandler:^{ | ||
GADAdReward *reward = ((GADRewardedAd*)rewarded_ad_).adReward; | ||
NotifyListenerOfUserEarnedReward( | ||
util::NSStringToString(reward.type), | ||
reward.amount.integerValue); | ||
}]; | ||
error_code = kAdMobErrorNone; | ||
error_message = nullptr; | ||
} | ||
CompleteFuture(error_code, error_message, handle, &future_data_); | ||
}); | ||
return MakeFuture(&future_data_.future_impl, handle); | ||
} | ||
|
||
void RewardedAdInternalIOS::RewardedAdDidReceiveAd(GADRewardedAd* ad) { | ||
firebase::MutexLock lock(mutex_); | ||
rewarded_ad_ = ad; | ||
ad.fullScreenContentDelegate = rewarded_ad_delegate_; | ||
ad.paidEventHandler = ^void(GADAdValue *_Nonnull adValue) { | ||
NotifyListenerOfPaidEvent( | ||
firebase::admob::ConvertGADAdValueToCppAdValue(adValue)); | ||
}; | ||
|
||
if (ad_load_callback_data_ != nil) { | ||
CompleteLoadAdInternalResult(ad_load_callback_data_, kAdMobErrorNone, | ||
/*error_message=*/""); | ||
ad_load_callback_data_ = nil; | ||
} | ||
} | ||
|
||
void RewardedAdInternalIOS::RewardedAdDidFailToReceiveAdWithError(NSError *gad_error) { | ||
firebase::MutexLock lock(mutex_); | ||
FIREBASE_ASSERT(gad_error); | ||
if (ad_load_callback_data_ != nil) { | ||
CompleteAdResultIOS(ad_load_callback_data_, gad_error); | ||
ad_load_callback_data_ = nil; | ||
} | ||
} | ||
|
||
} // namespace internal | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the model we use for other things that block until the destruction is complete? Or do we use a Semaphore?