diff --git a/README.md b/README.md
index f97eb555..18105476 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,21 @@ React Native Google Mobile Ads is built with three key principals in mind;
- 📄 **Well documented**
- full reference & installation documentation alongside detailed guides and FAQs
+## Migrating to the New Architecture Status (backwards compatible)
+This package can be used in both The Old and [The New Architecture](https://reactnative.dev/docs/the-new-architecture/landing-page).
+When using The New Architecture, some legacy code will still be used though. See status below:
+
+- **iOS**
+ - Mobile Ads SDK Methods (Turbo Native Module) 🟢🟢🟢🟢
+ - Banners (Fabric Native Component) 🟢🟢🟢🟢
+ - Full Screen Ads (Turbo Native Module) ⚪⚪⚪⚪
+ - User Messaging Platform (Turbo Native Module) ⚪⚪⚪⚪
+- **Android**
+ - Mobile Ads SDK Methods (Turbo Native Module) ⚪⚪⚪⚪
+ - Banners (Fabric Native Component) ⚪⚪⚪⚪
+ - Full Screen Ads (Turbo Native Module) ⚪⚪⚪⚪
+ - User Messaging Platform (Turbo Native Module) ⚪⚪⚪⚪
+
## Documentation
- [Installation](https://docs.page/invertase/react-native-google-mobile-ads)
diff --git a/RNGoogleMobileAds.podspec b/RNGoogleMobileAds.podspec
index 4fd3214b..b1266545 100644
--- a/RNGoogleMobileAds.podspec
+++ b/RNGoogleMobileAds.podspec
@@ -18,11 +18,11 @@ Pod::Spec.new do |s|
s.source = { :git => "#{package["repository"]["url"]}.git", :tag => "v#{s.version}" }
s.social_media_url = 'http://twitter.com/invertaseio'
s.ios.deployment_target = "10.0"
- s.source_files = 'ios/**/*.{h,m,swift}'
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
s.weak_frameworks = "AppTrackingTransparency"
# React Native dependencies
- s.dependency 'React-Core'
+ install_modules_dependencies(s)
# Other dependencies
if defined?($RNGoogleUmpSDKVersion)
diff --git a/__tests__/banner.test.tsx b/__tests__/banner.test.tsx
index 99be754c..6bc82168 100644
--- a/__tests__/banner.test.tsx
+++ b/__tests__/banner.test.tsx
@@ -26,4 +26,14 @@ describe('Google Mobile Ads Banner', function () {
"BannerAd: 'size(s)' expected a valid BannerAdSize or custom size string.",
);
});
+
+ it('throws if requestOptions is invalid.', function () {
+ let errorMsg;
+ try {
+ render();
+ } catch (e) {
+ errorMsg = e.message;
+ }
+ expect(errorMsg).toEqual("BannerAd: 'options' expected an object value");
+ });
});
diff --git a/__tests__/googleMobileAds.test.ts b/__tests__/googleMobileAds.test.ts
index bbe7817d..235d97a4 100644
--- a/__tests__/googleMobileAds.test.ts
+++ b/__tests__/googleMobileAds.test.ts
@@ -1,4 +1,5 @@
import admob, { MaxAdContentRating } from '../src';
+import RNGoogleMobileAdsModule from '../src/NativeGoogleMobileAdsModule';
describe('Admob', function () {
describe('setRequestConfiguration()', function () {
@@ -61,6 +62,26 @@ describe('Admob', function () {
});
describe('testDebugMenu', function () {
+ it('does call native initialize method', () => {
+ admob().initialize();
+ expect(RNGoogleMobileAdsModule.initialize).toBeCalledTimes(1);
+ });
+
+ it('does call native setRequestConfiguration method', () => {
+ admob().setRequestConfiguration({ tagForChildDirectedTreatment: true });
+ expect(RNGoogleMobileAdsModule.setRequestConfiguration).toBeCalledTimes(1);
+ });
+
+ it('does call native openAdInspector method', () => {
+ admob().openAdInspector();
+ expect(RNGoogleMobileAdsModule.openAdInspector).toBeCalledTimes(1);
+ });
+
+ it('does call native openDebugMenu method', () => {
+ admob().openDebugMenu('12345');
+ expect(RNGoogleMobileAdsModule.openDebugMenu).toBeCalledTimes(1);
+ });
+
it('throws if adUnit is empty', function () {
expect(() => {
admob().openDebugMenu('');
diff --git a/android/build.gradle b/android/build.gradle
index 761ed837..a393ca0f 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -41,6 +41,13 @@ def jsonMinSdk = packageJson['sdkVersions']['android']['minSdk']
def jsonTargetSdk = packageJson['sdkVersions']['android']['targetSdk']
def jsonCompileSdk = packageJson['sdkVersions']['android']['compileSdk']
def jsonBuildTools = packageJson['sdkVersions']['android']['buildTools']
+def isNewArchitectureEnabled() {
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
+}
+
+if (isNewArchitectureEnabled()) {
+ apply plugin: "com.facebook.react"
+}
project.ext {
set('react-native', [
@@ -100,6 +107,7 @@ android {
appJSONGoogleMobileAdsOptimizeInitialization : appJSONGoogleMobileAdsOptimizeInitializationBool,
appJSONGoogleMobileAdsOptimizeAdLoading : appJSONGoogleMobileAdsOptimizeAdLoadingBool
]
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
lintOptions {
disable 'GradleCompatible'
diff --git a/android/src/main/java/io/invertase/googlemobileads/OnNativeEvent.kt b/android/src/main/java/io/invertase/googlemobileads/OnNativeEvent.kt
new file mode 100644
index 00000000..33d722cc
--- /dev/null
+++ b/android/src/main/java/io/invertase/googlemobileads/OnNativeEvent.kt
@@ -0,0 +1,21 @@
+package io.invertase.googlemobileads
+
+import com.facebook.react.bridge.WritableMap
+import com.facebook.react.uimanager.events.Event
+
+class OnNativeEvent(viewId: Int, private val event: WritableMap) : Event(viewId) {
+
+ override fun getEventName(): String {
+ return EVENT_NAME
+ }
+
+ override fun getCoalescingKey(): Short = 0
+
+ override fun getEventData(): WritableMap? {
+ return event
+ }
+
+ companion object {
+ const val EVENT_NAME = "topNative"
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsBannerAdViewManager.java b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsBannerAdViewManager.java
index 89e6a3a5..0a98025f 100644
--- a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsBannerAdViewManager.java
+++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsBannerAdViewManager.java
@@ -22,14 +22,14 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
-import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.annotations.ReactProp;
-import com.facebook.react.uimanager.events.RCTEventEmitter;
+import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.view.ReactViewGroup;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest;
@@ -40,12 +40,15 @@
import com.google.android.gms.ads.admanager.AdManagerAdView;
import com.google.android.gms.ads.admanager.AppEventListener;
import io.invertase.googlemobileads.common.ReactNativeAdView;
+import io.invertase.googlemobileads.common.SharedUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.json.JSONException;
+import org.json.JSONObject;
public class ReactNativeGoogleMobileAdsBannerAdViewManager
extends SimpleViewManager {
@@ -73,7 +76,7 @@ public ReactNativeAdView createViewInstance(@Nonnull ThemedReactContext themedRe
@Override
public Map getExportedCustomDirectEventTypeConstants() {
MapBuilder.Builder builder = MapBuilder.builder();
- builder.put("onNativeEvent", MapBuilder.of("registrationName", "onNativeEvent"));
+ builder.put(OnNativeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onNativeEvent"));
return builder.build();
}
@@ -104,9 +107,15 @@ public void setUnitId(ReactNativeAdView reactViewGroup, String value) {
}
@ReactProp(name = "request")
- public void setRequest(ReactNativeAdView reactViewGroup, ReadableMap value) {
- reactViewGroup.setRequest(ReactNativeGoogleMobileAdsCommon.buildAdRequest(value));
- reactViewGroup.setPropsChanged(true);
+ public void setRequest(ReactNativeAdView reactViewGroup, String value) {
+ try {
+ JSONObject jsonObject = new JSONObject(value);
+ WritableMap writableMap = SharedUtils.jsonObjectToWritableMap(jsonObject);
+ reactViewGroup.setRequest(ReactNativeGoogleMobileAdsCommon.buildAdRequest(writableMap));
+ reactViewGroup.setPropsChanged(true);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
}
@ReactProp(name = "sizes")
@@ -274,8 +283,11 @@ private void sendEvent(ReactNativeAdView reactViewGroup, String type, WritableMa
event.merge(payload);
}
- ((ThemedReactContext) reactViewGroup.getContext())
- .getJSModule(RCTEventEmitter.class)
- .receiveEvent(reactViewGroup.getId(), "onNativeEvent", event);
+ ThemedReactContext themedReactContext = ((ThemedReactContext) reactViewGroup.getContext());
+ EventDispatcher eventDispatcher =
+ UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, reactViewGroup.getId());
+ if (eventDispatcher != null) {
+ eventDispatcher.dispatchEvent(new OnNativeEvent(reactViewGroup.getId(), event));
+ }
}
}
diff --git a/android/src/main/java/io/invertase/googlemobileads/common/SharedUtils.java b/android/src/main/java/io/invertase/googlemobileads/common/SharedUtils.java
index 7b5db7de..59fd0490 100644
--- a/android/src/main/java/io/invertase/googlemobileads/common/SharedUtils.java
+++ b/android/src/main/java/io/invertase/googlemobileads/common/SharedUtils.java
@@ -25,6 +25,7 @@
import android.os.Build;
import android.util.Log;
import com.facebook.react.bridge.*;
+import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.io.File;
@@ -239,28 +240,29 @@ public static Boolean hasPackageClass(String packageName, String className) {
}
public static WritableMap jsonObjectToWritableMap(JSONObject jsonObject) throws JSONException {
- Iterator iterator = jsonObject.keys();
- WritableMap writableMap = Arguments.createMap();
+ WritableMap map = new WritableNativeMap();
+ Iterator iterator = jsonObject.keys();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = jsonObject.get(key);
- if (value instanceof Float || value instanceof Double) {
- writableMap.putDouble(key, jsonObject.getDouble(key));
- } else if (value instanceof Number) {
- writableMap.putInt(key, jsonObject.getInt(key));
- } else if (value instanceof String) {
- writableMap.putString(key, jsonObject.getString(key));
- } else if (value instanceof JSONObject) {
- writableMap.putMap(key, jsonObjectToWritableMap(jsonObject.getJSONObject(key)));
+ if (value instanceof JSONObject) {
+ map.putMap(key, jsonObjectToWritableMap((JSONObject) value));
} else if (value instanceof JSONArray) {
- writableMap.putArray(key, jsonArrayToWritableArray(jsonObject.getJSONArray(key)));
- } else if (value == JSONObject.NULL) {
- writableMap.putNull(key);
+ map.putArray(key, jsonArrayToWritableArray((JSONArray) value));
+ } else if (value instanceof Boolean) {
+ map.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Integer) {
+ map.putInt(key, (Integer) value);
+ } else if (value instanceof Double) {
+ map.putDouble(key, (Double) value);
+ } else if (value instanceof String) {
+ map.putString(key, (String) value);
+ } else {
+ map.putString(key, value.toString());
}
}
-
- return writableMap;
+ return map;
}
public static WritableArray jsonArrayToWritableArray(JSONArray jsonArray) throws JSONException {
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerComponent.m b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerComponent.m
index 60a029be..e611e251 100644
--- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerComponent.m
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerComponent.m
@@ -66,8 +66,13 @@ - (void)setSizes:(NSArray *)sizes {
_propsChanged = true;
}
-- (void)setRequest:(NSDictionary *)request {
- _request = request;
+- (void)setRequest:(NSString *)request {
+ NSData *jsonData = [request dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error = nil;
+ _request = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
+ if (error) {
+ NSLog(@"Error parsing JSON: %@", error.localizedDescription);
+ }
_propsChanged = true;
}
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.h b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.h
new file mode 100644
index 00000000..a8c55b8c
--- /dev/null
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.h
@@ -0,0 +1,30 @@
+// This guard prevent this file to be compiled in the old architecture.
+#ifdef RCT_NEW_ARCH_ENABLED
+#import
+#import
+#import
+#import
+#import
+
+#ifndef NativeComponentExampleComponentView_h
+#define NativeComponentExampleComponentView_h
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RNGoogleMobileAdsBannerView
+ : RCTViewComponentView
+
+@property GADBannerView *banner;
+@property(nonatomic, assign) BOOL requested;
+
+@property(nonatomic, copy) NSArray *sizes;
+@property(nonatomic, copy) NSString *unitId;
+@property(nonatomic, copy) NSDictionary *request;
+@property(nonatomic, copy) NSNumber *manualImpressionsEnabled;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* NativeComponentExampleComponentView_h */
+#endif /* RCT_NEW_ARCH_ENABLED */
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.mm b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.mm
new file mode 100644
index 00000000..cd0234c7
--- /dev/null
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.mm
@@ -0,0 +1,220 @@
+// This guard prevent the code from being compiled in the old architecture
+#ifdef RCT_NEW_ARCH_ENABLED
+#import "RNGoogleMobileAdsBannerView.h"
+#import "RNGoogleMobileAdsCommon.h"
+
+#import
+#import
+#import
+#import
+
+#import "RCTFabricComponentsPlugins.h"
+
+using namespace facebook::react;
+
+@interface RNGoogleMobileAdsBannerView ()
+
+@end
+
+@implementation RNGoogleMobileAdsBannerView
+
++ (ComponentDescriptorProvider)componentDescriptorProvider {
+ return concreteComponentDescriptorProvider();
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ if (self = [super initWithFrame:frame]) {
+ static const auto defaultProps = std::make_shared();
+ _props = defaultProps;
+ }
+
+ return self;
+}
+
+- (void)prepareForRecycle {
+ [super prepareForRecycle];
+ static const auto defaultProps = std::make_shared();
+ _props = defaultProps;
+}
+
+- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
+ const auto &oldViewProps =
+ *std::static_pointer_cast(_props);
+ const auto &newViewProps =
+ *std::static_pointer_cast(props);
+
+ BOOL propsChanged = false;
+
+ if (oldViewProps.unitId != newViewProps.unitId) {
+ _unitId = [[NSString alloc] initWithUTF8String:newViewProps.unitId.c_str()];
+ propsChanged = true;
+ }
+
+ if (oldViewProps.sizes != newViewProps.sizes) {
+ NSMutableArray *adSizes = [NSMutableArray arrayWithCapacity:newViewProps.sizes.size()];
+ for (auto i = 0; i < newViewProps.sizes.size(); i++) {
+ NSString *jsonValue = [[NSString alloc] initWithUTF8String:newViewProps.sizes[i].c_str()];
+ GADAdSize adSize = [RNGoogleMobileAdsCommon stringToAdSize:jsonValue];
+ if (GADAdSizeEqualToSize(adSize, GADAdSizeInvalid)) {
+ RCTLogWarn(@"Invalid adSize %@", jsonValue);
+ } else {
+ [adSizes addObject:NSValueFromGADAdSize(adSize)];
+ }
+ }
+ _sizes = adSizes;
+ propsChanged = true;
+ }
+
+ if (_request == nil) {
+ _request = [NSDictionary dictionary];
+ }
+ if (oldViewProps.request != newViewProps.request) {
+ NSString *jsonString = [[NSString alloc] initWithUTF8String:newViewProps.request.c_str()];
+ NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error = nil;
+ _request = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
+ if (error) {
+ NSLog(@"Error parsing JSON: %@", error.localizedDescription);
+ }
+ propsChanged = true;
+ }
+
+ if (_manualImpressionsEnabled == nil) {
+ _manualImpressionsEnabled = [NSNumber numberWithBool:oldViewProps.manualImpressionsEnabled];
+ }
+ if (oldViewProps.manualImpressionsEnabled != newViewProps.manualImpressionsEnabled) {
+ _manualImpressionsEnabled = [NSNumber numberWithBool:newViewProps.manualImpressionsEnabled];
+ propsChanged = true;
+ }
+
+ if (propsChanged) {
+ [self requestAd];
+ }
+
+ [super updateProps:props oldProps:oldProps];
+}
+
+#pragma mark - Methods
+
+- (void)initBanner:(GADAdSize)adSize {
+ if (_requested) {
+ [_banner removeFromSuperview];
+ }
+ if ([RNGoogleMobileAdsCommon isAdManagerUnit:_unitId]) {
+ _banner = [[GAMBannerView alloc] initWithAdSize:adSize];
+
+ ((GAMBannerView *)_banner).validAdSizes = _sizes;
+ ((GAMBannerView *)_banner).appEventDelegate = self;
+ ((GAMBannerView *)_banner).enableManualImpressions = [_manualImpressionsEnabled boolValue];
+ } else {
+ _banner = [[GADBannerView alloc] initWithAdSize:adSize];
+ }
+ _banner.rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
+ _banner.delegate = self;
+}
+
+- (void)requestAd {
+#ifndef __LP64__
+ return; // prevent crash on 32bit
+#endif
+
+ if (_unitId == nil || _sizes == nil || _request == nil || _manualImpressionsEnabled == nil) {
+ [self setRequested:NO];
+ return;
+ } else {
+ [self initBanner:GADAdSizeFromNSValue(_sizes[0])];
+ [self addSubview:_banner];
+ _banner.adUnitID = _unitId;
+ [self setRequested:YES];
+ [_banner loadRequest:[RNGoogleMobileAdsCommon buildAdRequest:_request]];
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(
+ _eventEmitter)
+ ->onNativeEvent(facebook::react::RNGoogleMobileAdsBannerViewEventEmitter::OnNativeEvent{
+ .type = "onSizeChange",
+ .width = _banner.bounds.size.width,
+ .height = _banner.bounds.size.height});
+ }
+ }
+}
+
+- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
+ if ([commandName isEqual:@"recordManualImpression"]) {
+ [self recordManualImpression];
+ }
+}
+
+- (void)recordManualImpression {
+ if ([_banner class] == [GAMBannerView class]) {
+ [((GAMBannerView *)_banner) recordImpression];
+ }
+}
+
+#pragma mark - Events
+
+- (void)bannerViewDidReceiveAd:(GADBannerView *)bannerView {
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(
+ _eventEmitter)
+ ->onNativeEvent(facebook::react::RNGoogleMobileAdsBannerViewEventEmitter::OnNativeEvent{
+ .type = "onAdLoaded",
+ .width = bannerView.bounds.size.width,
+ .height = bannerView.bounds.size.height});
+ }
+}
+
+- (void)bannerView:(GADBannerView *)bannerView didFailToReceiveAdWithError:(NSError *)error {
+ NSDictionary *errorAndMessage = [RNGoogleMobileAdsCommon getCodeAndMessageFromAdError:error];
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(
+ _eventEmitter)
+ ->onNativeEvent(facebook::react::RNGoogleMobileAdsBannerViewEventEmitter::OnNativeEvent{
+ .type = "onAdFailedToLoad",
+ .code = std::string([[errorAndMessage valueForKey:@"code"] UTF8String]),
+ .message = std::string([[errorAndMessage valueForKey:@"message"] UTF8String])});
+ }
+}
+
+- (void)bannerViewWillPresentScreen:(GADBannerView *)bannerView {
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(
+ _eventEmitter)
+ ->onNativeEvent(facebook::react::RNGoogleMobileAdsBannerViewEventEmitter::OnNativeEvent{
+ .type = "onAdOpened"});
+ }
+}
+
+- (void)bannerViewWillDismissScreen:(GADBannerView *)bannerView {
+ // not in use
+}
+
+- (void)bannerViewDidDismissScreen:(GADBannerView *)bannerView {
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(
+ _eventEmitter)
+ ->onNativeEvent(facebook::react::RNGoogleMobileAdsBannerViewEventEmitter::OnNativeEvent{
+ .type = "onAdClosed"});
+ }
+}
+
+- (void)adView:(nonnull GADBannerView *)banner
+ didReceiveAppEvent:(nonnull NSString *)name
+ withInfo:(nullable NSString *)info {
+ if (_eventEmitter != nullptr) {
+ std::dynamic_pointer_cast(
+ _eventEmitter)
+ ->onNativeEvent(facebook::react::RNGoogleMobileAdsBannerViewEventEmitter::OnNativeEvent{
+ .type = "onAppEvent",
+ .name = std::string([name UTF8String]),
+ .data = std::string([info UTF8String])});
+ }
+}
+
+#pragma mark - RNGoogleMobileAdsBannerViewCls
+
+Class RNGoogleMobileAdsBannerViewCls(void) {
+ return RNGoogleMobileAdsBannerView.class;
+}
+
+@end
+#endif
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.h b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.h
deleted file mode 100644
index 489efbc6..00000000
--- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.h
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-/**
- * Copyright (c) 2016-present Invertase Limited & Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this library 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
-
-@interface RNGoogleMobileAdsBannerViewManager : RCTViewManager
-
-@end
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.m b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.mm
similarity index 89%
rename from ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.m
rename to ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.mm
index bbaf8921..3d3f8c45 100644
--- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.m
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.mm
@@ -16,9 +16,16 @@
*
*/
-#import "RNGoogleMobileAdsBannerViewManager.h"
#import
+#import
+#ifdef RCT_NEW_ARCH_ENABLE
+
+#else
#import "RNGoogleMobileAdsBannerComponent.h"
+#endif
+
+@interface RNGoogleMobileAdsBannerViewManager : RCTViewManager
+@end
@implementation RNGoogleMobileAdsBannerViewManager
@@ -28,7 +35,7 @@ @implementation RNGoogleMobileAdsBannerViewManager
RCT_EXPORT_VIEW_PROPERTY(unitId, NSString);
-RCT_EXPORT_VIEW_PROPERTY(request, NSDictionary);
+RCT_EXPORT_VIEW_PROPERTY(request, NSString);
RCT_EXPORT_VIEW_PROPERTY(manualImpressionsEnabled, BOOL);
@@ -48,6 +55,9 @@ @implementation RNGoogleMobileAdsBannerViewManager
#endif
}
+#ifdef RCT_NEW_ARCH_ENABLE
+
+#else
@synthesize bridge = _bridge;
- (UIView *)view {
@@ -62,5 +72,6 @@ - (UIView *)view {
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
+#endif
@end
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.h b/ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.h
index 5c47ca34..2ee019aa 100644
--- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.h
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.h
@@ -18,8 +18,8 @@
#if !TARGET_OS_MACCATALYST
+#import
#import
-@import GoogleMobileAds;
@interface RNGoogleMobileAdsCommon : NSObject
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.m b/ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.mm
similarity index 100%
rename from ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.m
rename to ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.mm
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.h b/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.h
index 13c1c1df..5aeb0b3b 100644
--- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.h
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.h
@@ -17,8 +17,16 @@
#import
-#import
+#ifdef RCT_NEW_ARCH_ENABLED
+
+#import
+@interface RNGoogleMobileAdsModule : NSObject
+
+#else
+#import
@interface RNGoogleMobileAdsModule : NSObject
+#endif
+
@end
diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.m b/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm
similarity index 86%
rename from ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.m
rename to ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm
index d4941d15..a2f47e4d 100644
--- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.m
+++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm
@@ -21,6 +21,9 @@
#import
#import "RNGoogleMobileAdsModule.h"
+#ifdef RCT_NEW_ARCH_ENABLED
+#import "RNGoogleMobileAdsSpec.h"
+#endif
#import "common/RNSharedUtils.h"
@implementation RNGoogleMobileAdsModule
@@ -37,6 +40,41 @@ - (dispatch_queue_t)methodQueue {
#pragma mark Google Mobile Ads Methods
RCT_EXPORT_METHOD(initialize : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) {
+ [self initialize:resolve reject:reject];
+}
+
+RCT_EXPORT_METHOD(setRequestConfiguration
+ : (NSDictionary *)requestConfiguration
+ : (RCTPromiseResolveBlock)resolve
+ : (RCTPromiseRejectBlock)reject) {
+ [self setRequestConfiguration:requestConfiguration resolve:resolve reject:reject];
+}
+
+RCT_EXPORT_METHOD(openAdInspector
+ : (RCTPromiseResolveBlock)resolve
+ : (RCTPromiseRejectBlock)reject) {
+ [self openAdInspector:resolve reject:reject];
+}
+
+RCT_EXPORT_METHOD(openDebugMenu : (NSString *)adUnit) {
+#if !TARGET_OS_MACCATALYST
+ GADDebugOptionsViewController *debugOptionsViewController =
+ [GADDebugOptionsViewController debugOptionsViewControllerWithAdUnitID:adUnit];
+ [RCTSharedApplication().delegate.window.rootViewController
+ presentViewController:debugOptionsViewController
+ animated:YES
+ completion:nil];
+#endif
+}
+
+#ifdef RCT_NEW_ARCH_ENABLED
+- (std::shared_ptr)getTurboModule:
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
+ return std::make_shared(params);
+}
+#endif
+
+- (void)initialize:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
#if !TARGET_OS_MACCATALYST
[[GADMobileAds sharedInstance]
startWithCompletionHandler:^(GADInitializationStatus *_Nonnull status) {
@@ -56,15 +94,9 @@ - (dispatch_queue_t)methodQueue {
#endif
}
-RCT_EXPORT_METHOD(setRequestConfiguration
- : (NSDictionary *)requestConfiguration
- : (RCTPromiseResolveBlock)resolve
- : (RCTPromiseRejectBlock)reject) {
- [self setRequestConfiguration:requestConfiguration];
- resolve([NSNull null]);
-}
-
-- (void)setRequestConfiguration:(NSDictionary *)requestConfiguration {
+- (void)setRequestConfiguration:(NSDictionary *)requestConfiguration
+ resolve:(RCTPromiseResolveBlock)resolve
+ reject:(RCTPromiseRejectBlock)reject {
#if !TARGET_OS_MACCATALYST
if (requestConfiguration[@"maxAdContentRating"]) {
NSString *rating = requestConfiguration[@"maxAdContentRating"];
@@ -104,12 +136,12 @@ - (void)setRequestConfiguration:(NSDictionary *)requestConfiguration {
}
GADMobileAds.sharedInstance.requestConfiguration.testDeviceIdentifiers = devices;
}
+
+ resolve([NSNull null]);
#endif
}
-RCT_EXPORT_METHOD(openAdInspector
- : (RCTPromiseResolveBlock)resolve
- : (RCTPromiseRejectBlock)reject) {
+- (void)openAdInspector:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
#if !TARGET_OS_MACCATALYST
[GADMobileAds.sharedInstance
presentAdInspectorFromViewController:RCTSharedApplication().delegate.window.rootViewController
@@ -129,15 +161,4 @@ - (void)setRequestConfiguration:(NSDictionary *)requestConfiguration {
#endif
}
-RCT_EXPORT_METHOD(openDebugMenu : (NSString *)adUnit) {
-#if !TARGET_OS_MACCATALYST
- GADDebugOptionsViewController *debugOptionsViewController =
- [GADDebugOptionsViewController debugOptionsViewControllerWithAdUnitID:adUnit];
- [RCTSharedApplication().delegate.window.rootViewController
- presentViewController:debugOptionsViewController
- animated:YES
- completion:nil];
-#endif
-}
-
@end
diff --git a/jest.setup.ts b/jest.setup.ts
index 7c821350..3f3a2896 100644
--- a/jest.setup.ts
+++ b/jest.setup.ts
@@ -27,6 +27,16 @@ jest.doMock('react-native', () => {
RNGoogleMobileAdsRewardedModule: {},
RNGoogleMobileAdsConsentModule: {},
},
+ TurboModuleRegistry: {
+ getEnforcing: () => {
+ return {
+ initialize: jest.fn(),
+ setRequestConfiguration: jest.fn(),
+ openAdInspector: jest.fn(),
+ openDebugMenu: jest.fn(),
+ };
+ },
+ },
},
ReactNative,
);
diff --git a/package.json b/package.json
index 37ddb42e..6a084b68 100644
--- a/package.json
+++ b/package.json
@@ -153,5 +153,13 @@
},
"publishConfig": {
"access": "public"
+ },
+ "codegenConfig": {
+ "name": "RNGoogleMobileAdsSpec",
+ "type": "all",
+ "jsSrcsDir": "./src",
+ "android": {
+ "javaPackageName": "io.invertase.googlemobileads"
+ }
}
}
diff --git a/src/MobileAds.ts b/src/MobileAds.ts
index 6730929b..318d2509 100644
--- a/src/MobileAds.ts
+++ b/src/MobileAds.ts
@@ -1,13 +1,10 @@
-import { NativeModules } from 'react-native';
-
+import RNGoogleMobileAdsModule from './NativeGoogleMobileAdsModule';
import { validateAdRequestConfiguration } from './validateAdRequestConfiguration';
import { SharedEventEmitter } from './internal/SharedEventEmitter';
import { GoogleMobileAdsNativeEventEmitter } from './internal/GoogleMobileAdsNativeEventEmitter';
import { MobileAdsModuleInterface } from './types/MobileAdsModule.interface';
import { RequestConfiguration } from './types/RequestConfiguration';
-const { RNGoogleMobileAdsModule } = NativeModules;
-
const NATIVE_MODULE_EVENT_SUBSCRIPTIONS: Record = {};
const nativeEvents = [
diff --git a/src/NativeGoogleMobileAdsModule.ts b/src/NativeGoogleMobileAdsModule.ts
new file mode 100644
index 00000000..a5308222
--- /dev/null
+++ b/src/NativeGoogleMobileAdsModule.ts
@@ -0,0 +1,14 @@
+import type { TurboModule } from 'react-native';
+import { TurboModuleRegistry } from 'react-native';
+import { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
+
+import { AdapterStatus } from './types';
+
+export interface Spec extends TurboModule {
+ initialize(): Promise;
+ setRequestConfiguration(requestConfiguration?: UnsafeObject): Promise;
+ openAdInspector(): Promise;
+ openDebugMenu(adUnit: string): void;
+}
+
+export default TurboModuleRegistry.getEnforcing('RNGoogleMobileAdsModule');
diff --git a/src/ads/BaseAd.tsx b/src/ads/BaseAd.tsx
index 20352548..ea8cdf72 100644
--- a/src/ads/BaseAd.tsx
+++ b/src/ads/BaseAd.tsx
@@ -17,142 +17,127 @@
*/
import React, { useState, useEffect } from 'react';
-import { NativeMethods, requireNativeComponent } from 'react-native';
+import { NativeSyntheticEvent } from 'react-native';
import { isFunction } from '../common';
import { NativeError } from '../internal/NativeError';
+import GoogleMobileAdsBannerView from './GoogleMobileAdsBannerViewNativeComponent';
+import type { NativeEvent } from './GoogleMobileAdsBannerViewNativeComponent';
import { BannerAdSize, GAMBannerAdSize } from '../BannerAdSize';
import { validateAdRequestOptions } from '../validateAdRequestOptions';
import { GAMBannerAdProps } from '../types/BannerAdProps';
-import { RequestOptions } from '../types/RequestOptions';
-
-type NativeEvent =
- | {
- type: 'onAdLoaded' | 'onSizeChange';
- width: number;
- height: number;
- }
- | { type: 'onAdOpened' | 'onAdClosed' }
- | {
- type: 'onAdFailedToLoad';
- code: string;
- message: string;
- }
- | {
- type: 'onAppEvent';
- name: string;
- data?: string;
- };
const sizeRegex = /([0-9]+)x([0-9]+)/;
-export const BaseAd = React.forwardRef(
- ({ unitId, sizes, requestOptions, manualImpressionsEnabled, ...props }, ref) => {
- const [dimensions, setDimensions] = useState<(number | string)[]>([0, 0]);
+export const BaseAd = React.forwardRef<
+ React.ElementRef,
+ GAMBannerAdProps
+>(({ unitId, sizes, requestOptions, manualImpressionsEnabled, ...props }, ref) => {
+ const [dimensions, setDimensions] = useState<(number | string)[]>([0, 0]);
- useEffect(() => {
- if (!unitId) {
- throw new Error("BannerAd: 'unitId' expected a valid string unit ID.");
- }
- }, [unitId]);
+ useEffect(() => {
+ if (!unitId) {
+ throw new Error("BannerAd: 'unitId' expected a valid string unit ID.");
+ }
+ }, [unitId]);
- useEffect(() => {
- if (
- sizes.length === 0 ||
- !sizes.every(
- size => size in BannerAdSize || size in GAMBannerAdSize || sizeRegex.test(size),
- )
- ) {
- throw new Error("BannerAd: 'size(s)' expected a valid BannerAdSize or custom size string.");
- }
- }, [sizes]);
+ useEffect(() => {
+ if (
+ sizes.length === 0 ||
+ !sizes.every(size => size in BannerAdSize || size in GAMBannerAdSize || sizeRegex.test(size))
+ ) {
+ throw new Error("BannerAd: 'size(s)' expected a valid BannerAdSize or custom size string.");
+ }
+ }, [sizes]);
- const parsedRequestOptions = JSON.stringify(requestOptions);
+ const parsedRequestOptions = JSON.stringify(requestOptions);
- useEffect(() => {
- if (requestOptions) {
- try {
- validateAdRequestOptions(requestOptions);
- } catch (e) {
- if (e instanceof Error) {
- throw new Error(`BannerAd: ${e.message}`);
- }
+ useEffect(() => {
+ if (requestOptions) {
+ try {
+ validateAdRequestOptions(requestOptions);
+ } catch (e) {
+ if (e instanceof Error) {
+ throw new Error(`BannerAd: ${e.message}`);
}
}
- }, [parsedRequestOptions]);
-
- function onNativeEvent({ nativeEvent }: { nativeEvent: NativeEvent }) {
- const { type } = nativeEvent;
+ }
+ }, [parsedRequestOptions]);
- if (type !== 'onSizeChange' && isFunction(props[type])) {
- let eventHandler, eventPayload;
- switch (type) {
- case 'onAdLoaded':
- eventPayload = {
- width: nativeEvent.width,
- height: nativeEvent.height,
- };
- if ((eventHandler = props[type])) eventHandler(eventPayload);
- break;
- case 'onAdFailedToLoad':
- eventPayload = NativeError.fromEvent(nativeEvent, 'googleMobileAds');
- if ((eventHandler = props[type])) eventHandler(eventPayload);
- break;
- case 'onAppEvent':
- eventPayload = {
- name: nativeEvent.name,
- data: nativeEvent.data,
- };
- if ((eventHandler = props[type])) eventHandler(eventPayload);
- break;
- default:
- if ((eventHandler = props[type])) eventHandler();
+ function onNativeEvent(event: NativeSyntheticEvent) {
+ const nativeEvent = event.nativeEvent as
+ | {
+ type: 'onAdLoaded' | 'onSizeChange';
+ width: number;
+ height: number;
}
+ | { type: 'onAdOpened' | 'onAdClosed' }
+ | {
+ type: 'onAdFailedToLoad';
+ code: string;
+ message: string;
+ }
+ | {
+ type: 'onAppEvent';
+ name: string;
+ data?: string;
+ };
+ const { type } = nativeEvent;
+
+ if (type !== 'onSizeChange' && isFunction(props[type])) {
+ let eventHandler, eventPayload;
+ switch (type) {
+ case 'onAdLoaded':
+ eventPayload = {
+ width: nativeEvent.width,
+ height: nativeEvent.height,
+ };
+ if ((eventHandler = props[type])) eventHandler(eventPayload);
+ break;
+ case 'onAdFailedToLoad':
+ eventPayload = NativeError.fromEvent(nativeEvent, 'googleMobileAds');
+ if ((eventHandler = props[type])) eventHandler(eventPayload);
+ break;
+ case 'onAppEvent':
+ eventPayload = {
+ name: nativeEvent.name,
+ data: nativeEvent.data,
+ };
+ if ((eventHandler = props[type])) eventHandler(eventPayload);
+ break;
+ default:
+ if ((eventHandler = props[type])) eventHandler();
}
+ }
- if (type === 'onAdLoaded' || type === 'onSizeChange') {
- const { width, height } = nativeEvent;
- if (width && height) setDimensions([width, height]);
+ if (type === 'onAdLoaded' || type === 'onSizeChange') {
+ const width = Math.ceil(nativeEvent.width);
+ const height = Math.ceil(nativeEvent.height);
+ if (width && height && JSON.stringify([width, height]) !== JSON.stringify(dimensions)) {
+ setDimensions([width, height]);
}
}
+ }
- const style = sizes.includes(GAMBannerAdSize.FLUID)
- ? {
- width: '100%',
- height: dimensions[1],
- }
- : {
- width: dimensions[0],
- height: dimensions[1],
- };
+ const style = sizes.includes(GAMBannerAdSize.FLUID)
+ ? {
+ width: '100%',
+ height: dimensions[1],
+ }
+ : {
+ width: dimensions[0],
+ height: dimensions[1],
+ };
- return (
-
- );
- },
-);
+ return (
+
+ );
+});
BaseAd.displayName = 'BaseAd';
-
-interface NativeBannerProps {
- sizes: GAMBannerAdProps['sizes'];
- style: {
- width?: number | string;
- height?: number | string;
- };
- unitId: string;
- request: RequestOptions;
- manualImpressionsEnabled: boolean;
- onNativeEvent: (event: { nativeEvent: NativeEvent }) => void;
-}
-
-const GoogleMobileAdsBannerView = requireNativeComponent(
- 'RNGoogleMobileAdsBannerView',
-);
-export type GoogleMobileAdsBannerView = React.Component & NativeMethods;
diff --git a/src/ads/GAMBannerAd.tsx b/src/ads/GAMBannerAd.tsx
index 2cc6e490..d8a0214f 100644
--- a/src/ads/GAMBannerAd.tsx
+++ b/src/ads/GAMBannerAd.tsx
@@ -16,20 +16,17 @@
*/
import React, { createRef } from 'react';
-import { findNodeHandle, Platform, UIManager } from 'react-native';
import { GAMBannerAdProps } from '../types/BannerAdProps';
-import { BaseAd, GoogleMobileAdsBannerView } from './BaseAd';
+import { BaseAd } from './BaseAd';
+import GoogleMobileAdsBannerView, { Commands } from './GoogleMobileAdsBannerViewNativeComponent';
export class GAMBannerAd extends React.Component {
- private ref = createRef();
+ private ref = createRef>();
recordManualImpression() {
- let commandID: string | number = UIManager.getViewManagerConfig('RNGoogleMobileAdsBannerView')
- .Commands.recordManualImpression;
- if (Platform.OS === 'android') {
- commandID = commandID.toString();
+ if (this.ref.current) {
+ Commands.recordManualImpression(this.ref.current);
}
- UIManager.dispatchViewManagerCommand(findNodeHandle(this.ref.current), commandID, undefined);
}
render() {
diff --git a/src/ads/GoogleMobileAdsBannerViewNativeComponent.ts b/src/ads/GoogleMobileAdsBannerViewNativeComponent.ts
new file mode 100644
index 00000000..de0ddeb8
--- /dev/null
+++ b/src/ads/GoogleMobileAdsBannerViewNativeComponent.ts
@@ -0,0 +1,37 @@
+import type * as React from 'react';
+import type { HostComponent, ViewProps } from 'react-native';
+import type { BubblingEventHandler, Float } from 'react-native/Libraries/Types/CodegenTypes';
+import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
+
+export type NativeEvent = {
+ type: string;
+ width?: Float;
+ height?: Float;
+ code?: string;
+ message?: string;
+ name?: string;
+ data?: string;
+};
+
+export interface NativeProps extends ViewProps {
+ sizes: string[];
+ unitId: string;
+ request: string;
+ manualImpressionsEnabled: boolean;
+ onNativeEvent: BubblingEventHandler;
+}
+
+export type ComponentType = HostComponent;
+
+interface NativeCommands {
+ recordManualImpression: (viewRef: React.ElementRef) => void;
+}
+
+export const Commands: NativeCommands = codegenNativeCommands({
+ supportedCommands: ['recordManualImpression'],
+});
+
+export default codegenNativeComponent(
+ 'RNGoogleMobileAdsBannerView',
+) as HostComponent;
diff --git a/src/types/GoogleMobileAdsNativeModule.ts b/src/types/GoogleMobileAdsNativeModule.ts
deleted file mode 100644
index da12711f..00000000
--- a/src/types/GoogleMobileAdsNativeModule.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { AdapterStatus } from './AdapterStatus';
-import { RequestConfiguration } from './RequestConfiguration';
-
-export interface GoogleMobileAdsNativeModule {
- initialize(): Promise;
- setRequestConfiguration(requestConfiguration?: RequestConfiguration): Promise;
- openAdInspector(): Promise;
- openDebugMenu(adUnit: string): void;
-}