Skip to content
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

feat: [WIP] Native Ad Support #650

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 119 additions & 13 deletions RNGoogleMobileAdsExample/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {useEffect, useRef} from 'react';
import React, {RefObject, useEffect, useRef, useState} from 'react';
import {
Button,
Image,
Platform,
SafeAreaView,
ScrollView,
Expand All @@ -11,26 +12,32 @@ import {
import {Test, TestRegistry, TestResult, TestRunner, TestType} from 'jet';

import MobileAds, {
type PaidEvent,
AdEventType,
AdsConsent,
AdsConsentDebugGeography,
AppOpenAd,
InterstitialAd,
TestIds,
BannerAd,
BannerAdSize,
GAMAdEventType,
GAMBannerAd,
GAMBannerAdSize,
GAMInterstitialAd,
InterstitialAd,
NativeAd,
NativeAdView,
NativeAsset,
NativeAssetType,
NativeMediaAspectRatio,
NativeMediaView,
type PaidEvent,
RevenuePrecisions,
RewardedAd,
RewardedAdEventType,
useInterstitialAd,
RewardedInterstitialAd,
TestIds,
useAppOpenAd,
useInterstitialAd,
useRewardedAd,
GAMInterstitialAd,
GAMAdEventType,
GAMBannerAd,
RewardedInterstitialAd,
useRewardedInterstitialAd,
} from 'react-native-google-mobile-ads';

Expand Down Expand Up @@ -184,8 +191,9 @@ class InterstitialTest implements Test {

class BannerTest implements Test {
bannerAdSize: BannerAdSize | string;
bannerRef: RefObject<BannerAd>;

constructor(bannerAdSize) {
constructor(bannerAdSize: BannerAdSize) {
this.bannerAdSize = bannerAdSize;
this.bannerRef = React.createRef();
}
Expand Down Expand Up @@ -442,6 +450,103 @@ class RewardedInterstitialTest implements Test {
}
}

const NativeComponent = () => {
const [nativeAd, setNativeAd] = useState<NativeAd>();

useEffect(() => {
NativeAd.createForAdRequest(TestIds.GAM_NATIVE, {
aspectRatio: NativeMediaAspectRatio.LANDSCAPE,
})
.then(setNativeAd)
.catch(console.error);
}, []);

if (!nativeAd) {
return null;
}

return (
<NativeAdView
nativeAd={nativeAd}>
<View style={{ padding: 16, gap: 8 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
{nativeAd.icon && (
<NativeAsset assetKey={NativeAssetType.ICON}>
<Image source={{ uri: nativeAd.icon.url }} width={24} height={24} />
</NativeAsset>
)}
<NativeAsset assetKey={NativeAssetType.HEADLINE}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>{nativeAd.headline}</Text>
</NativeAsset>
<Text style={{
backgroundColor: '#FBBC04',
color: 'white',
paddingHorizontal: 4,
paddingVertical: 2,
fontWeight: 'bold',
fontSize: 11,
borderRadius: 4,
}}>
AD
</Text>
</View>
{nativeAd.advertiser && (
<NativeAsset assetKey={NativeAssetType.ADVERTISER}>
<Text>{nativeAd.advertiser}</Text>
</NativeAsset>
)}
<NativeAsset assetKey={NativeAssetType.BODY}>
<Text>{nativeAd.body}</Text>
</NativeAsset>
</View>
<NativeMediaView />
<NativeAsset assetKey={NativeAssetType.CALL_TO_ACTION}>
<Text style={{
color: 'white',
fontWeight: 'bold',
backgroundColor: '#4285F4',
paddingHorizontal: 16,
paddingVertical: 12,
}}>
{nativeAd.callToAction}
</Text>
</NativeAsset>
</NativeAdView>
);
};

class NativeTest implements Test {
constructor() {
}

getPath(): string {
return 'Native';
}

getTestType(): TestType {
return TestType.Interactive;
}

render(onMount: (component: any) => void): React.ReactNode {
return (
<View ref={onMount}>
<NativeComponent />
</View>
);
}

execute(component: any, complete: (result: TestResult) => void): void {
let results = new TestResult();
try {
// You can do anything here, it will execute on-device + in-app. Results are aggregated + visible in-app.
} catch (error) {
results.errors.push('Received unexpected error...');
} finally {
complete(results);
}
}
}

class AdConsentTest implements Test {
getPath(): string {
return 'ConsentForm';
Expand Down Expand Up @@ -800,7 +905,7 @@ const GAMBannerComponent = React.forwardRef<
View,
{
unitId: string;
sizes: GAMBannerAdSize[];
sizes: (keyof typeof GAMBannerAdSize)[];
}
>(({unitId, sizes}, ref) => {
const bannerRef = useRef<GAMBannerAd>(null);
Expand Down Expand Up @@ -830,7 +935,7 @@ class GAMBannerTest implements Test {
constructor(
private readonly props: {
unitId: string;
sizes: GAMBannerAdSize[];
sizes: (keyof typeof GAMBannerAdSize)[];
},
) {}

Expand Down Expand Up @@ -993,7 +1098,7 @@ class DebugMenuTest implements Test {
}

// All tests must be registered - a future feature will allow auto-bundling of tests via configured path or regex
Object.keys(BannerAdSize).forEach(bannerAdSize => {
Object.values(BannerAdSize).forEach(bannerAdSize => {
TestRegistry.registerTest(new BannerTest(bannerAdSize));
});
TestRegistry.registerTest(new CollapsibleBannerTest());
Expand All @@ -1006,6 +1111,7 @@ TestRegistry.registerTest(new InterstitialHookTest());
TestRegistry.registerTest(new RewardedHookTest());
TestRegistry.registerTest(new AppOpenHookTest());
TestRegistry.registerTest(new RewardedInterstitialHookTest());
TestRegistry.registerTest(new NativeTest());
TestRegistry.registerTest(new AdInspectorTest());
TestRegistry.registerTest(
new GAMBannerTest({
Expand Down
2 changes: 1 addition & 1 deletion RNGoogleMobileAdsExample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1820,4 +1820,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: f699c82614f0340e3985855a1efdaa77a260037e

COCOAPODS: 1.16.1
COCOAPODS: 1.16.2
2 changes: 1 addition & 1 deletion __tests__/googleMobileAds.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import admob, { MaxAdContentRating } from '../src';
import RNGoogleMobileAdsModule from '../src/NativeGoogleMobileAdsModule';
import RNGoogleMobileAdsModule from '../src/specs/modules/NativeGoogleMobileAdsModule';

describe('Admob', function () {
describe('setRequestConfiguration()', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
import java.util.Map;

public class ReactNativeAppModule extends ReactNativeModule {
private static final String TAG = "RNAppModule";
static final String NAME = "RNAppModule";

ReactNativeAppModule(ReactApplicationContext reactContext) {
super(reactContext, TAG);
super(reactContext, NAME);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@

public class ReactNativeGoogleMobileAdsConsentModule extends ReactNativeModule {

private static final String TAG = "RNGoogleMobileAdsConsentModule";
static final String NAME = "RNGoogleMobileAdsConsentModule";
private ConsentInformation consentInformation;

public ReactNativeGoogleMobileAdsConsentModule(ReactApplicationContext reactContext) {
super(reactContext, TAG);
super(reactContext, NAME);
consentInformation = UserMessagingPlatform.getConsentInformation(reactContext);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.invertase.googlemobileads

/*
* 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 android.annotation.SuppressLint
import com.facebook.react.bridge.ReactContext
import com.google.android.gms.ads.nativead.MediaView

@SuppressLint("ViewConstructor")
class ReactNativeGoogleMobileAdsMediaView(
private val context: ReactContext
): MediaView(context) {
fun setResponseId(responseId: String?) {
val nativeModule = context.getNativeModule(ReactNativeGoogleMobileAdsNativeModule.NAME) as ReactNativeGoogleMobileAdsNativeModule?
nativeModule?.getNativeAd(responseId ?: "")?.let {
this.mediaContent = it.mediaContent
requestLayout()
}
}

override fun requestLayout() {
super.requestLayout()
post(measureAndLayout)
}

private val measureAndLayout = Runnable {
measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
layout(left, top, right, bottom)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.invertase.googlemobileads

/*
* 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 com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.viewmanagers.RNGoogleMobileAdsMediaViewManagerDelegate
import com.facebook.react.viewmanagers.RNGoogleMobileAdsMediaViewManagerInterface

@ReactModule(name = ReactNativeGoogleMobileAdsMediaViewManager.NAME)
class ReactNativeGoogleMobileAdsMediaViewManager(
reactContext: ReactApplicationContext
) : ViewGroupManager<ReactNativeGoogleMobileAdsMediaView>(reactContext),
RNGoogleMobileAdsMediaViewManagerInterface<ReactNativeGoogleMobileAdsMediaView> {
private val delegate: ViewManagerDelegate<ReactNativeGoogleMobileAdsMediaView> = RNGoogleMobileAdsMediaViewManagerDelegate(this)

override fun getDelegate(): ViewManagerDelegate<ReactNativeGoogleMobileAdsMediaView> = delegate

override fun getName(): String = NAME

override fun createViewInstance(context: ThemedReactContext): ReactNativeGoogleMobileAdsMediaView = ReactNativeGoogleMobileAdsMediaView(context)

@ReactProp(name = "responseId")
override fun setResponseId(mediaView: ReactNativeGoogleMobileAdsMediaView, responseId: String?) {
mediaView.setResponseId(responseId)
}

companion object {
const val NAME = "RNGoogleMobileAdsMediaView"
}
}
Loading
Loading