Skip to content

Commit

Permalink
feat: app open ads
Browse files Browse the repository at this point in the history
  • Loading branch information
dylancom authored and mikehardy committed Jan 26, 2022
1 parent 6cb9449 commit f145716
Show file tree
Hide file tree
Showing 19 changed files with 520 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
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 static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsCommon.getCodeAndMessageFromAdError;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsCommon.sendAdEvent;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsEvent.GOOGLE_MOBILE_ADS_EVENT_APP_OPEN;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsEvent.GOOGLE_MOBILE_ADS_EVENT_CLICKED;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsEvent.GOOGLE_MOBILE_ADS_EVENT_CLOSED;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsEvent.GOOGLE_MOBILE_ADS_EVENT_ERROR;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsEvent.GOOGLE_MOBILE_ADS_EVENT_LOADED;
import static io.invertase.googlemobileads.ReactNativeGoogleMobileAdsEvent.GOOGLE_MOBILE_ADS_EVENT_OPENED;

import android.app.Activity;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.appopen.AppOpenAd;
import io.invertase.googlemobileads.common.ReactNativeModule;
import javax.annotation.Nullable;

public class ReactNativeGoogleMobileAdsAppOpenModule extends ReactNativeModule {
private static final String SERVICE = "RNGoogleMobileAdsAppOpenModule";
private AppOpenAd appOpenAd = null;

public ReactNativeGoogleMobileAdsAppOpenModule(ReactApplicationContext reactContext) {
super(reactContext, SERVICE);
}

private void sendAppOpenEvent(
String type, int requestId, String adUnitId, @Nullable WritableMap error) {
sendAdEvent(GOOGLE_MOBILE_ADS_EVENT_APP_OPEN, requestId, type, adUnitId, error);
}

@ReactMethod
public void appOpenLoad(int requestId, String adUnitId, ReadableMap adRequestOptions) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
WritableMap error = Arguments.createMap();
error.putString("code", "null-activity");
error.putString(
"message", "App Open ad attempted to load but the current Activity was null.");
sendAppOpenEvent(GOOGLE_MOBILE_ADS_EVENT_ERROR, requestId, adUnitId, error);
return;
}
currentActivity.runOnUiThread(
() -> {
AdRequest.Builder adRequestBuilder = new AdRequest.Builder();
AdRequest adRequest = adRequestBuilder.build();

AppOpenAd.AppOpenAdLoadCallback appOpenAdLoadCallback =
new AppOpenAd.AppOpenAdLoadCallback() {

@Override
public void onAdLoaded(@NonNull AppOpenAd appOpenAd) {

appOpenAd.setFullScreenContentCallback(
new FullScreenContentCallback() {
@Override
public void onAdDismissedFullScreenContent() {
sendAppOpenEvent(
GOOGLE_MOBILE_ADS_EVENT_CLOSED, requestId, adUnitId, null);
ReactNativeGoogleMobileAdsAppOpenModule.this.appOpenAd = null;
}

@Override
public void onAdClicked() {
sendAppOpenEvent(
GOOGLE_MOBILE_ADS_EVENT_CLICKED, requestId, adUnitId, null);
}

@Override
public void onAdShowedFullScreenContent() {
sendAppOpenEvent(
GOOGLE_MOBILE_ADS_EVENT_OPENED, requestId, adUnitId, null);
}
});

ReactNativeGoogleMobileAdsAppOpenModule.this.appOpenAd = appOpenAd;
sendAppOpenEvent(GOOGLE_MOBILE_ADS_EVENT_LOADED, requestId, adUnitId, null);
}

@Override
public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
WritableMap error = Arguments.createMap();
String[] codeAndMessage = getCodeAndMessageFromAdError(loadAdError);
error.putString("code", codeAndMessage[0]);
error.putString("message", codeAndMessage[1]);
sendAppOpenEvent(GOOGLE_MOBILE_ADS_EVENT_ERROR, requestId, adUnitId, error);
}
};

AppOpenAd.load(
currentActivity,
adUnitId,
adRequest,
AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT,
appOpenAdLoadCallback);
});
}

@ReactMethod
public void appOpenShow(int requestId, ReadableMap showOptions, Promise promise) {
if (getCurrentActivity() == null) {
rejectPromiseWithCodeAndMessage(
promise,
"null-activity",
"App Open ad attempted to show but the current Activity was null.");
return;
}
getCurrentActivity()
.runOnUiThread(
() -> {
if (appOpenAd == null) {
rejectPromiseWithCodeAndMessage(
promise,
"null-appOpenAd",
"App Open ad attempted to show but its object was null.");
return;
}

if (showOptions.hasKey("immersiveModeEnabled")) {
appOpenAd.setImmersiveMode(showOptions.getBoolean("immersiveModeEnabled"));
} else {
appOpenAd.setImmersiveMode(false);
}

String a = String.valueOf(requestId);

if (appOpenAd != null) {
appOpenAd.show(getCurrentActivity());
promise.resolve(null);
} else {
rejectPromiseWithCodeAndMessage(
promise, "not-ready", "App Open ad attempted to show but was not ready.");
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.invertase.googlemobileads.interfaces.NativeEvent;

public class ReactNativeGoogleMobileAdsEvent implements NativeEvent {
public static final String GOOGLE_MOBILE_ADS_EVENT_APP_OPEN = "google_mobile_ads_app_open_event";
public static final String GOOGLE_MOBILE_ADS_EVENT_INTERSTITIAL =
"google_mobile_ads_interstitial_event";
public static final String GOOGLE_MOBILE_ADS_EVENT_REWARDED = "google_mobile_ads_rewarded_event";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
modules.add(new ReactNativeAppModule(reactContext));
modules.add(new ReactNativeGoogleMobileAdsModule(reactContext));
modules.add(new ReactNativeGoogleMobileAdsConsentModule(reactContext));
modules.add(new ReactNativeGoogleMobileAdsAppOpenModule(reactContext));
modules.add(new ReactNativeGoogleMobileAdsInterstitialModule(reactContext));
modules.add(new ReactNativeGoogleMobileAdsRewardedModule(reactContext));
return modules;
Expand Down
59 changes: 59 additions & 0 deletions docs/displaying-ads.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,65 @@

The AdMob package allows you to display three types of adverts; Interstitial, Rewarded & Banner.

## App Open Ads

App open ads are a special ad format intended for publishers wishing to monetize their app load screens.
App open ads can be closed by your users at any time. App open ads can be shown when users bring your app to the foreground.

App open ads are shown when your application launches or when users bring your application to the foreground.
To make sure you have an ad ready to display when a user opens your app, you'll want to have a reference to an ad ready to use.

That means you must preload a app open ad before you need to show the ad. That way, your app open ad is ready to show the next time the app is opened.

To create a new app open ad, call the `createForAdRequest` method from the `AppOpenAd` class. The first argument
of the method is the "Ad Unit ID". For testing, we can use a Test ID, however for production the ID from the
Google AdMob dashboard under "Ad units" should be used:

```js
import { AppOpenAd, TestIds, AdEventType } from 'react-native-google-mobile-ads';

const adUnitId = __DEV__ ? TestIds.APP_OPEN : 'ca-app-pub-xxxxxxxxxxxxx/yyyyyyyyyyyyyy';

const appOpenAd = AppOpenAd.createForAdRequest(adUnitId, {
requestNonPersonalizedAdsOnly: true,
keywords: ['fashion', 'clothing'],
});

// Preload an app open ad
appOpenAd.load()

// Show the app open ad when user brings the app to the foreground.
appOpenAd.show()

```

### Consider ad expiration

Key Point: Ad references in the app open beta will time out after four hours.
Ads rendered more than four hours after request time will no longer be valid and may not earn revenue.
This time limit is being carefully considered and may change in future beta versions of the app open format.

### Cold starts and loading screens

The above documentation assumes that you only show app open ads when users foreground your app when it is suspended in memory.
"Cold starts" occur when your app is launched but was not previously suspended in memory.

An example of a cold start is when a user opens your app for the first time.
With cold starts, you won't have a previously loaded app open ad that's ready to be shown right away.
The delay between when you request an ad and receive an ad back can create a situation where users are able to briefly use your app before being surprised by an out of context ad.
This should be avoided because it is a bad user experience.

The preferred way to use app open ads on cold starts is to use a loading screen to load your game or app assets, and to only show the ad from the loading screen.
If your app has completed loading and has sent the user to the main content of your app, do not show the ad.

### Best practices

Google built app open ads to help you monetize your app's loading screen, but it's important to keep best practices in mind so that your users enjoy using your app. Make sure to:

- Wait to show your first app open ad until after your users have used your app a few times.
- Show app open ads during times when your users would otherwise be waiting for your app to load.
- If you have a loading screen under the app open ad, and your loading screen completes loading before the ad is dismissed, you may want to dismiss your loading screen after receiving the `onAdClosed` event.

## Interstitial Ads

Interstitials are full-screen ads that cover the interface of an app until closed by the user. These type of ads are
Expand Down
13 changes: 9 additions & 4 deletions docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ a [Google AdMob account](https://apps.admob.com) is required.

The module supports three types of Ads:

1. Full screen [Interstitial Ads](/displaying-ads#interstitial-ads).
2. Full screen [Rewarded Ads](/displaying-ads#rewarded-ads).
3. Component based [Banner Ads](/displaying-ads#banner-ads).
1. Full screen [App Open Ads](/displaying-ads#app-open-ads).
2. Full screen [Interstitial Ads](/displaying-ads#interstitial-ads).
3. Full screen [Rewarded Ads](/displaying-ads#rewarded-ads).
4. Component based [Banner Ads](/displaying-ads#banner-ads).

# Getting Started

Expand Down Expand Up @@ -141,7 +142,10 @@ Although usage of different advert types is explained later, when creating your
testing can be set to a testing ID. The code snippet below shows how to initialize each advert type with a test ID:

```jsx
import { InterstitialAd, RewardedAd, BannerAd, TestIds } from 'react-native-google-mobile-ads';
import { AppOpenAd, InterstitialAd, RewardedAd, BannerAd, TestIds } from 'react-native-google-mobile-ads';

# App Open
AppOpenAd.createForAdRequest(TestIds.APP_OPEN);

# Interstitial
InterstitialAd.createForAdRequest(TestIds.INTERSTITIAL);
Expand All @@ -158,6 +162,7 @@ RewardedAd.createForAdRequest(TestIds.REWARDED);
Now the basics of setting up and configuring AdMob have been explained, we can go ahead and start to display different
adverts to our users. The AdMob module provides integration with three different types:

- [App Open Ads](/displaying-ads#app-open-ads)
- [Interstitial Ads](/displaying-ads#interstitial-ads)
- [Rewarded Ads](/displaying-ads#rewarded-ads)
- [Banner Ads](/displaying-ads#banner-ads)
50 changes: 48 additions & 2 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,58 @@ import {Button, SafeAreaView, ScrollView, StyleSheet, View} from 'react-native';
import {Test, TestRegistry, TestResult, TestRunner, TestType} from 'jet';

import {
AppOpenAd,
InterstitialAd,
TestIds,
BannerAd,
BannerAdSize,
RewardedAd,
} from 'react-native-google-mobile-ads';

const appOpenAdUnitId = TestIds.APP_OPEN;

const appOpen = AppOpenAd.createForAdRequest(appOpenAdUnitId, {
requestNonPersonalizedAdsOnly: true,
});

class AppOpenTest implements Test {
constructor() {
appOpen.load();
}

getPath(): string {
return 'App open';
}

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

render(onMount: (component: any) => void): React.ReactNode {
return (
<View style={styles.testSpacing} ref={onMount}>
<Button
title="Show App Open Ad"
onPress={() => {
appOpen.show();
}}
/>
</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);
}
}
}

const interstitialAdUnitId = TestIds.INTERSTITIAL;

const interstitial = InterstitialAd.createForAdRequest(interstitialAdUnitId, {
Expand Down Expand Up @@ -69,10 +114,10 @@ class BannerTest implements Test {

render(onMount: (component: any) => void): React.ReactNode {
return (
<View style={styles.testSpacing} ref={onMount}>
<View ref={onMount}>
<BannerAd
unitId={bannerAdUnitId}
size={BannerAdSize.FULL_BANNER}
size={BannerAdSize.ADAPTIVE_BANNER}
requestOptions={{
requestNonPersonalizedAdsOnly: true,
}}
Expand Down Expand Up @@ -139,6 +184,7 @@ class RewaredTest implements Test {

// All tests must be registered - a future feature will allow auto-bundling of tests via configured path or regex
TestRegistry.registerTest(new BannerTest());
TestRegistry.registerTest(new AppOpenTest());
TestRegistry.registerTest(new InterstitialTest());
TestRegistry.registerTest(new RewaredTest());

Expand Down
29 changes: 29 additions & 0 deletions ios/RNGoogleMobileAds/RNGoogleMobileAdsAppOpenModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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 <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import "RNGoogleMobileAdsFullScreenContentDelegate.h"

@import GoogleMobileAds;

@interface RNGoogleMobileAdsAppOpenModule : NSObject <RCTBridgeModule>

@property(strong, nonatomic) GADAppOpenAd* appOpenAd;
@property(strong, nonatomic) RNGoogleMobileAdsFullScreenContentDelegate* appOpenDelegate;

@end
Loading

0 comments on commit f145716

Please sign in to comment.