Skip to content

Commit dc996e0

Browse files
authored
AdMob Rewarded Ads on iOS (#752)
Updates the iOS implement to be able to display rewarded ads.
1 parent b57f5a7 commit dc996e0

File tree

6 files changed

+263
-16
lines changed

6 files changed

+263
-16
lines changed

admob/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ set(ios_SRCS
5151
src/ios/FADBannerView.mm
5252
src/ios/FADInterstitialDelegate.mm
5353
src/ios/FADRequest.mm
54+
src/ios/FADRewardedAdDelegate.mm
5455
src/ios/ad_result_ios.mm
5556
src/ios/adapter_response_info_ios.mm
5657
src/ios/admob_ios.mm

admob/integration_test/src/integration_test.cc

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdLoad) {
588588

589589
TEST_F(FirebaseAdMobTest, TestRewardedAdLoad) {
590590
SKIP_TEST_ON_DESKTOP;
591-
SKIP_TEST_ON_IOS;
592591

593592
// Note: while showing an ad requires user interaction (below),
594593
// we test that we can simply load an ad first.
@@ -726,7 +725,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdLoadAndShow) {
726725
TEST_F(FirebaseAdMobTest, TestRewardedAdLoadAndShow) {
727726
TEST_REQUIRES_USER_INTERACTION;
728727
SKIP_TEST_ON_DESKTOP;
729-
SKIP_TEST_ON_IOS;
730728

731729
firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd();
732730

@@ -1272,7 +1270,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdErrorBadExtrasClassName) {
12721270

12731271
TEST_F(FirebaseAdMobTest, TestRewardedAdErrorNotInitialized) {
12741272
SKIP_TEST_ON_DESKTOP;
1275-
SKIP_TEST_ON_IOS;
12761273

12771274
firebase::admob::RewardedAd* rewarded_ad = new firebase::admob::RewardedAd();
12781275

@@ -1287,7 +1284,6 @@ TEST_F(FirebaseAdMobTest, TestRewardedAdErrorNotInitialized) {
12871284

12881285
TEST_F(FirebaseAdMobTest, TesRewardedAdErrorAlreadyInitialized) {
12891286
SKIP_TEST_ON_DESKTOP;
1290-
SKIP_TEST_ON_IOS;
12911287

12921288
{
12931289
firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd();
@@ -1327,7 +1323,6 @@ TEST_F(FirebaseAdMobTest, TesRewardedAdErrorAlreadyInitialized) {
13271323

13281324
TEST_F(FirebaseAdMobTest, TestRewardedAdErrorLoadInProgress) {
13291325
SKIP_TEST_ON_DESKTOP;
1330-
SKIP_TEST_ON_IOS;
13311326

13321327
firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd();
13331328
WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()),
@@ -1362,7 +1357,6 @@ TEST_F(FirebaseAdMobTest, TestRewardedAdErrorLoadInProgress) {
13621357

13631358
TEST_F(FirebaseAdMobTest, TestRewardedAdErrorBadAdUnitId) {
13641359
SKIP_TEST_ON_DESKTOP;
1365-
SKIP_TEST_ON_IOS;
13661360

13671361
firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd();
13681362
WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()),
@@ -1389,7 +1383,6 @@ TEST_F(FirebaseAdMobTest, TestRewardedAdErrorBadAdUnitId) {
13891383

13901384
TEST_F(FirebaseAdMobTest, TestRewardedAdErrorBadExtrasClassName) {
13911385
SKIP_TEST_ON_DESKTOP;
1392-
SKIP_TEST_ON_IOS;
13931386

13941387
firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd();
13951388
WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()),
@@ -1446,7 +1439,6 @@ TEST_F(FirebaseAdMobTest, TestInterstitialAdStress) {
14461439
TEST_F(FirebaseAdMobTest, TestRewardedAdStress) {
14471440
TEST_REQUIRES_USER_INTERACTION;
14481441
SKIP_TEST_ON_DESKTOP;
1449-
SKIP_TEST_ON_IOS;
14501442

14511443
for (int i = 0; i < 10; ++i) {
14521444
firebase::admob::RewardedAd* rewarded = new firebase::admob::RewardedAd();

admob/src/ios/FADRewardedAdDelegate.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// An Objective-C++ wrapper class that conforms to the
18+
// GADRewardedAdDelegate protocol. When the delegate for receiving state
19+
// change messages from a GADRewardedAd is notified, this wrapper class
20+
// forwards the notification to the RewardedAdInternalIOS object to handle
21+
// the state changes for an rewarded ad.
22+
23+
#import <Foundation/Foundation.h>
24+
#import <GoogleMobileAds/GoogleMobileAds.h>
25+
26+
namespace firebase {
27+
namespace admob {
28+
namespace internal {
29+
class RewardedAdInternalIOS;
30+
} // namespace internal
31+
} // namespace admob
32+
} // namespace firebase
33+
34+
NS_ASSUME_NONNULL_BEGIN
35+
36+
@interface FADRewardedAdDelegate : NSObject<GADFullScreenContentDelegate>
37+
38+
/// Returns a FADInterstitialDelegate object with InterstitialAdInternalIOS.
39+
- (FADRewardedAdDelegate *)initWithInternalRewardedAd:
40+
(firebase::admob::internal::RewardedAdInternalIOS *)rewardedAd;
41+
42+
@end
43+
44+
NS_ASSUME_NONNULL_END
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "admob/src/ios/FADRewardedAdDelegate.h"
18+
19+
#include "admob/src/ios/ad_result_ios.h"
20+
#include "admob/src/ios/rewarded_ad_internal_ios.h"
21+
22+
@interface FADRewardedAdDelegate () {
23+
/// The RewardedAdInternalIOS object.
24+
firebase::admob::internal::RewardedAdInternalIOS *_rewardedAd;
25+
}
26+
@end
27+
28+
@implementation FADRewardedAdDelegate : NSObject
29+
30+
#pragma mark - Initialization
31+
32+
- (instancetype)initWithInternalRewardedAd:
33+
(firebase::admob::internal::RewardedAdInternalIOS *)rewardedAd {
34+
self = [super init];
35+
if (self) {
36+
_rewardedAd = rewardedAd;
37+
}
38+
39+
return self;
40+
}
41+
42+
#pragma mark - GADFullScreenContentDelegate
43+
44+
// Capture AdMob iOS Full screen events and forward them to our C++
45+
// translation layer.
46+
- (void)adDidRecordImpression:(nonnull id<GADFullScreenPresentingAd>)ad {
47+
_rewardedAd->NotifyListenerOfAdImpression();
48+
}
49+
50+
- (void)adDidRecordClick:(nonnull id<GADFullScreenPresentingAd>)ad {
51+
_rewardedAd->NotifyListenerOfAdClickedFullScreenContent();
52+
}
53+
54+
- (void)ad:(nonnull id<GADFullScreenPresentingAd>)ad
55+
didFailToPresentFullScreenContentWithError:(nonnull NSError *)error {
56+
firebase::admob::AdResultInternal ad_result_internal;
57+
ad_result_internal.is_wrapper_error = false;
58+
ad_result_internal.is_successful = false;
59+
ad_result_internal.ios_error = error;
60+
// Invoke AdMobInternal, a friend of AdResult, to have it access its
61+
// protected constructor with the AdError data.
62+
const firebase::admob::AdResult& ad_result = firebase::admob::AdMobInternal::CreateAdResult(ad_result_internal);
63+
_rewardedAd->NotifyListenerOfAdFailedToShowFullScreenContent(ad_result);
64+
}
65+
66+
- (void)adDidPresentFullScreenContent:(nonnull id<GADFullScreenPresentingAd>)ad {
67+
_rewardedAd->NotifyListenerOfAdShowedFullScreenContent();
68+
}
69+
70+
- (void)adDidDismissFullScreenContent:(nonnull id<GADFullScreenPresentingAd>)ad {
71+
_rewardedAd->NotifyListenerOfAdDismissedFullScreenContent();
72+
}
73+
74+
@end

admob/src/ios/rewarded_ad_internal_ios.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
#ifndef FIREBASE_ADMOB_SRC_IOS_REWARDED_AD_INTERNAL_IOS_H_
1818
#define FIREBASE_ADMOB_SRC_IOS_REWARDED_AD_INTERNAL_IOS_H_
1919

20+
#ifdef __OBJC__
21+
#import "admob/src/ios/FADRewardedAdDelegate.h"
22+
#endif // __OBJC__
23+
2024
extern "C" {
2125
#include <objc/objc.h>
2226
} // extern "C"
@@ -39,8 +43,15 @@ class RewardedAdInternalIOS : public RewardedAdInternal {
3943
Future<void> Show(UserEarnedRewardListener* listener) override;
4044
bool is_initialized() const override { return initialized_; }
4145

46+
#ifdef __OBJC__
47+
void RewardedAdDidReceiveAd(GADRewardedAd* ad);
48+
void RewardedAdDidFailToReceiveAdWithError(NSError *gad_error);
49+
void RewardedAdWillPresentScreen();
50+
void RewardedAdDidDismissScreen();
51+
#endif // __OBJC__
52+
4253
private:
43-
/// Prevents duplicate invocations of initailize on the Interstitial Ad.
54+
/// Prevents duplicate invocations of initailize on the Rewarded Ad.
4455
bool initialized_;
4556

4657
/// Contains information to asynchronously complete the LoadAd Future.

admob/src/ios/rewarded_ad_internal_ios.mm

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,148 @@
3030
ad_load_callback_data_(nil), rewarded_ad_(nil),
3131
parent_view_(nil), rewarded_ad_delegate_(nil) {}
3232

33-
RewardedAdInternalIOS::~RewardedAdInternalIOS() { }
33+
RewardedAdInternalIOS::~RewardedAdInternalIOS() {
34+
firebase::MutexLock lock(mutex_);
35+
// Clean up any resources created in RewardedAdInternalIOS.
36+
Mutex mutex(Mutex::kModeNonRecursive);
37+
__block Mutex *mutex_in_block = &mutex;
38+
mutex.Acquire();
39+
void (^destroyBlock)() = ^{
40+
((GADRewardedAd*)rewarded_ad_).fullScreenContentDelegate = nil;
41+
rewarded_ad_delegate_ = nil;
42+
rewarded_ad_ = nil;
43+
if(ad_load_callback_data_ != nil) {
44+
delete ad_load_callback_data_;
45+
ad_load_callback_data_ = nil;
46+
}
47+
mutex_in_block->Release();
48+
};
49+
util::DispatchAsyncSafeMainQueue(destroyBlock);
50+
mutex.Acquire();
51+
mutex.Release();
52+
}
3453

3554
Future<void> RewardedAdInternalIOS::Initialize(AdParent parent) {
3655
firebase::MutexLock lock(mutex_);
37-
return CreateAndCompleteFuture(
38-
kRewardedAdFnInitialize, kAdMobErrorNone, nullptr, &future_data_);
56+
const SafeFutureHandle<void> future_handle =
57+
future_data_.future_impl.SafeAlloc<void>(kRewardedAdFnInitialize);
58+
59+
if(initialized_) {
60+
CompleteFuture(kAdMobErrorAlreadyInitialized,
61+
kAdAlreadyInitializedErrorMessage, future_handle, &future_data_);
62+
} else {
63+
initialized_ = true;
64+
parent_view_ = (UIView *)parent;
65+
CompleteFuture(kAdMobErrorNone, nullptr, future_handle, &future_data_);
66+
}
67+
return MakeFuture(&future_data_.future_impl, future_handle);
3968
}
4069

4170
Future<AdResult> RewardedAdInternalIOS::LoadAd(
4271
const char* ad_unit_id, const AdRequest& request) {
43-
return CreateAndCompleteFutureWithResult(
44-
kRewardedAdFnLoadAd, kAdMobErrorNone, nullptr, &future_data_, AdResult());
72+
firebase::MutexLock lock(mutex_);
73+
FutureCallbackData<AdResult>* callback_data =
74+
CreateAdResultFutureCallbackData(kRewardedAdFnLoadAd,
75+
&future_data_);
76+
SafeFutureHandle<AdResult> future_handle = callback_data->future_handle;
77+
78+
if (ad_load_callback_data_ != nil) {
79+
CompleteLoadAdInternalResult(callback_data, kAdMobErrorLoadInProgress,
80+
kAdLoadInProgressErrorMessage);
81+
return MakeFuture(&future_data_.future_impl, future_handle);
82+
}
83+
84+
// Persist a pointer to the callback data so that we may use it after the iOS
85+
// SDK returns the AdResult.
86+
ad_load_callback_data_ = callback_data;
87+
88+
rewarded_ad_delegate_ =
89+
[[FADRewardedAdDelegate alloc] initWithInternalRewardedAd:this];
90+
91+
dispatch_async(dispatch_get_main_queue(), ^{
92+
// Create a GADRequest from an admob::AdRequest.
93+
AdMobError error_code = kAdMobErrorNone;
94+
std::string error_message;
95+
GADRequest *ad_request =
96+
GADRequestFromCppAdRequest(request, &error_code, &error_message);
97+
if (ad_request == nullptr) {
98+
if (error_code == kAdMobErrorNone) {
99+
error_code = kAdMobErrorInternalError;
100+
error_message = kAdCouldNotParseAdRequestErrorMessage;
101+
}
102+
CompleteLoadAdInternalResult(ad_load_callback_data_, error_code,
103+
error_message.c_str());
104+
ad_load_callback_data_ = nil;
105+
} else {
106+
// Make the rewarded ad request.
107+
[GADRewardedAd loadWithAdUnitID:@(ad_unit_id)
108+
request:ad_request
109+
completionHandler:^(GADRewardedAd *ad, NSError *error) // NO LINT
110+
{
111+
if (error) {
112+
RewardedAdDidFailToReceiveAdWithError(error);
113+
} else {
114+
RewardedAdDidReceiveAd(ad);
115+
}
116+
}];
117+
}
118+
});
119+
120+
return MakeFuture(&future_data_.future_impl, future_handle);
45121
}
46122

47123
Future<void> RewardedAdInternalIOS::Show(UserEarnedRewardListener* listener) {
48-
return CreateAndCompleteFuture(
49-
kRewardedAdFnShow, kAdMobErrorNone, nullptr, &future_data_);
124+
firebase::MutexLock lock(mutex_);
125+
const firebase::SafeFutureHandle<void> handle =
126+
future_data_.future_impl.SafeAlloc<void>(kRewardedAdFnShow);
127+
user_earned_reward_listener_ = listener;
128+
129+
dispatch_async(dispatch_get_main_queue(), ^{
130+
AdMobError error_code = kAdMobErrorLoadInProgress;
131+
const char* error_message = kAdLoadInProgressErrorMessage;
132+
if (rewarded_ad_ == nil) {
133+
error_code = kAdMobErrorUninitialized;
134+
error_message = kAdUninitializedErrorMessage;
135+
} else {
136+
[rewarded_ad_
137+
presentFromRootViewController:[parent_view_ window].rootViewController
138+
userDidEarnRewardHandler:^{
139+
GADAdReward *reward = ((GADRewardedAd*)rewarded_ad_).adReward;
140+
NotifyListenerOfUserEarnedReward(
141+
util::NSStringToString(reward.type),
142+
reward.amount.integerValue);
143+
}];
144+
error_code = kAdMobErrorNone;
145+
error_message = nullptr;
146+
}
147+
CompleteFuture(error_code, error_message, handle, &future_data_);
148+
});
149+
return MakeFuture(&future_data_.future_impl, handle);
150+
}
151+
152+
void RewardedAdInternalIOS::RewardedAdDidReceiveAd(GADRewardedAd* ad) {
153+
firebase::MutexLock lock(mutex_);
154+
rewarded_ad_ = ad;
155+
ad.fullScreenContentDelegate = rewarded_ad_delegate_;
156+
ad.paidEventHandler = ^void(GADAdValue *_Nonnull adValue) {
157+
NotifyListenerOfPaidEvent(
158+
firebase::admob::ConvertGADAdValueToCppAdValue(adValue));
159+
};
160+
161+
if (ad_load_callback_data_ != nil) {
162+
CompleteLoadAdInternalResult(ad_load_callback_data_, kAdMobErrorNone,
163+
/*error_message=*/"");
164+
ad_load_callback_data_ = nil;
165+
}
166+
}
167+
168+
void RewardedAdInternalIOS::RewardedAdDidFailToReceiveAdWithError(NSError *gad_error) {
169+
firebase::MutexLock lock(mutex_);
170+
FIREBASE_ASSERT(gad_error);
171+
if (ad_load_callback_data_ != nil) {
172+
CompleteAdResultIOS(ad_load_callback_data_, gad_error);
173+
ad_load_callback_data_ = nil;
174+
}
50175
}
51176

52177
} // namespace internal

0 commit comments

Comments
 (0)