diff --git a/android/src/main/java/com/rnadmob/admob/RNAdMobCommon.java b/android/src/main/java/com/rnadmob/admob/RNAdMobCommon.java index c517813..226e7a7 100644 --- a/android/src/main/java/com/rnadmob/admob/RNAdMobCommon.java +++ b/android/src/main/java/com/rnadmob/admob/RNAdMobCommon.java @@ -132,4 +132,6 @@ static public AdManagerAdRequest buildAdRequest(ReadableMap requestOptions) { return builder.build(); } + + } diff --git a/android/src/main/java/com/rnadmob/admob/RNAdMobPromiseHolder.java b/android/src/main/java/com/rnadmob/admob/RNAdMobPromiseHolder.java index 10cd959..69fb8a6 100644 --- a/android/src/main/java/com/rnadmob/admob/RNAdMobPromiseHolder.java +++ b/android/src/main/java/com/rnadmob/admob/RNAdMobPromiseHolder.java @@ -1,8 +1,11 @@ package com.rnadmob.admob; +import static com.rnadmob.admob.RNAdMobEventModule.AD_FAILED_TO_PRESENT; + import android.util.SparseArray; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.WritableMap; import com.google.android.gms.ads.AdError; import java.util.Locale; @@ -26,19 +29,10 @@ public void resolve(int requestId) { } } - public void reject(int requestId, AdError adError) { - Promise promise = promiseArray.get(requestId); - if (promise != null) { - String code = String.format(Locale.getDefault(),"E_AD_PRESENT_FAILED(%d)", adError.getCode()); - promise.reject(code, adError.getMessage()); - promiseArray.delete(requestId); - } - } - - public void reject(int requestId, String code, String message) { + public void reject(int requestId, WritableMap error) { Promise promise = promiseArray.get(requestId); if (promise != null) { - promise.reject(code, message); + promise.reject(AD_FAILED_TO_PRESENT, "Error occurred while presenting ad.", error); promiseArray.delete(requestId); } } diff --git a/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobAppOpenAdModule.java b/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobAppOpenAdModule.java index 038a103..0c5d89c 100644 --- a/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobAppOpenAdModule.java +++ b/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobAppOpenAdModule.java @@ -1,6 +1,6 @@ package com.rnadmob.admob.ads.fullscreen; -import static com.rnadmob.admob.RNAdMobEventModule.AD_FAILED_TO_PRESENT; +import static com.rnadmob.admob.RNAdMobEventModule.AD_FAILED_TO_LOAD; import android.os.Handler; @@ -9,7 +9,6 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; -import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMethod; @@ -98,10 +97,9 @@ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { @Override protected void show(AppOpenAd ad, int requestId) { if (isAdExpired()) { - presentPromiseHolder.reject(requestId, "E_AD_NOT_READY", "Ad is expired."); - WritableMap error = Arguments.createMap(); - error.putString("message", "Ad is expired."); - sendEvent(AD_FAILED_TO_PRESENT, requestId, error); + WritableMap error = createErrorObject(null, "Ad is expired."); + sendError(AD_FAILED_TO_LOAD, requestId, null, error); + requestAd(requestId, unitId, options, null); return; } @@ -131,5 +129,4 @@ public void onStart(@NonNull LifecycleOwner owner) { } } } - } diff --git a/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobFullScreenAdModule.java b/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobFullScreenAdModule.java index 5f5aded..b7e5d28 100644 --- a/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobFullScreenAdModule.java +++ b/android/src/main/java/com/rnadmob/admob/ads/fullscreen/RNAdMobFullScreenAdModule.java @@ -25,7 +25,6 @@ import com.rnadmob.admob.RNAdMobEventModule; import com.rnadmob.admob.RNAdMobPromiseHolder; -import java.util.Locale; import java.util.Objects; public abstract class RNAdMobFullScreenAdModule extends ActivityAwareJavaModule { @@ -60,12 +59,33 @@ protected void sendEvent(String eventName, int requestId, @Nullable WritableMap RNAdMobEventModule.sendEvent(eventName, getAdType(), requestId, data); } - protected Activity getCurrentActivity(Promise promise) { - Activity activity = super.getCurrentActivity(); - if (activity == null && promise != null) { - promise.reject("E_NULL_ACTIVITY", "Cannot process Ad because the current Activity is null."); + protected void sendError(String eventName, int requestId, @Nullable Promise promise, WritableMap error) { + if (promise != null) { + String message; + if (eventName.equals(AD_FAILED_TO_LOAD)) { + message = "Error occurred while loading ad."; + } else { + message = "Error occurred while showing ad."; + } + promise.reject(eventName, message, error.copy()); + } else if (eventName.equals(AD_FAILED_TO_PRESENT)) { + presentPromiseHolder.reject(requestId, error.copy()); } - return activity; + sendEvent(eventName, requestId, error.copy()); + } + + protected WritableMap createErrorObject(@Nullable Integer code, String message) { + WritableMap error = Arguments.createMap(); + if (code == null) + error.putNull("code"); + else + error.putInt("code", code); + error.putString("message", message); + return error; + } + + protected WritableMap createErrorObject(AdError adError) { + return createErrorObject(adError.getCode(), adError.getMessage()); } private AdLoadCallback getAdLoadCallback(int requestId, ReadableMap options, Promise promise) { @@ -95,15 +115,8 @@ public void onAdLoaded(@NonNull T ad) { @Override public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { - if (promise != null) { - String code = String.format(Locale.getDefault(), "E_AD_LOAD_FAILED(%d)", loadAdError.getCode()); - promise.reject(code, loadAdError.getMessage()); - } - - WritableMap error = Arguments.createMap(); - error.putInt("code", loadAdError.getCode()); - error.putString("message", loadAdError.getMessage()); - sendEvent(AD_FAILED_TO_LOAD, requestId, error); + WritableMap error = createErrorObject(loadAdError); + sendError(AD_FAILED_TO_LOAD, requestId, promise, error); if (getAdType().equals(RNAdMobAppOpenAdModule.AD_TYPE)) { if (!RNAdMobAppOpenAdModule.appStarted) { @@ -136,12 +149,8 @@ public void onAdDismissedFullScreenContent() { @Override public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) { - presentPromiseHolder.reject(requestId, adError); - - WritableMap error = Arguments.createMap(); - error.putInt("code", adError.getCode()); - error.putString("message", adError.getMessage()); - sendEvent(AD_FAILED_TO_PRESENT, requestId, error); + WritableMap error = createErrorObject(adError); + sendError(AD_FAILED_TO_PRESENT, requestId, null, error); adHolder.remove(requestId); } @@ -149,7 +158,6 @@ public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) { @Override public void onAdShowedFullScreenContent() { presentPromiseHolder.resolve(requestId); - sendEvent(AD_PRESENTED, requestId, null); } }; @@ -157,6 +165,8 @@ public void onAdShowedFullScreenContent() { protected void requestAd(int requestId, String unitId, ReadableMap options, final Promise promise) { if (currentActivity == null) { + WritableMap error = createErrorObject(null, "Current activity is null."); + sendError(AD_FAILED_TO_LOAD, requestId, promise, error); return; } @@ -171,6 +181,8 @@ protected void requestAd(int requestId, String unitId, ReadableMap options, fina protected void presentAd(int requestId, final Promise promise) { if (currentActivity == null) { + WritableMap error = createErrorObject(null, "Current activity is null."); + sendError(AD_FAILED_TO_PRESENT, requestId, promise, error); return; } @@ -180,18 +192,16 @@ protected void presentAd(int requestId, final Promise promise) { presentPromiseHolder.add(requestId, promise); show(ad, requestId); } else { - if (promise != null) { - promise.reject("E_AD_NOT_READY", "Ad is not ready."); - } - WritableMap error = Arguments.createMap(); - error.putString("message", "Ad is not ready."); - sendEvent(AD_FAILED_TO_PRESENT, requestId, error); + WritableMap error = createErrorObject(null, "Ad is not loaded."); + sendError(AD_FAILED_TO_PRESENT, requestId, promise, error); } }); } protected void destroyAd(int requestId) { adHolder.remove(requestId); - presentPromiseHolder.reject(requestId, "E_AD_DESTROYED", "Ad has been destroyed."); + + WritableMap error = createErrorObject(null, "Ad has been destroyed."); + presentPromiseHolder.reject(requestId, error); } } diff --git a/docs/docs/usage/appopen.mdx b/docs/docs/usage/appopen.mdx index aa9da50..2999810 100644 --- a/docs/docs/usage/appopen.mdx +++ b/docs/docs/usage/appopen.mdx @@ -40,6 +40,12 @@ An example of a cold start is when a user opens your app for the first time. Wit 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. +## Accessing Ad's status + +You can access to ad's status by using derived values of `useAppOpenAd` hook. + +If you are using Class API instead of using hook, add listeners to listen for ad's status changes such as `adDismissed` event. + ## Usage Example Example below uses external library [react-native-bootsplash](https://github.com/zoontek/react-native-bootsplash) to implement splash screen. diff --git a/ios/Ads/FullScreen/RNAdMobAppOpen.swift b/ios/Ads/FullScreen/RNAdMobAppOpen.swift index 92b1683..36cf1e7 100644 --- a/ios/Ads/FullScreen/RNAdMobAppOpen.swift +++ b/ios/Ads/FullScreen/RNAdMobAppOpen.swift @@ -55,10 +55,9 @@ class RNAdMobAppOpen: RNAdMobFullScreenAd { override func show(ad: GADAppOpenAd, viewController: UIViewController, requestId: Int) { if (isAdExpired()) { - presentPromiseHolder.reject(requestId: requestId, code: "E_AD_NOT_READY", message: "Ad is expired") - var error = Dictionary() - error.updateValue("Ad is expired", forKey: "message") - sendEvent(eventName: kEventAdFailedToPresent, requestId: requestId, data: error) + let errorData = createErrorData(code: nil, message: "Ad is expired.") + sendError(eventName: kEventAdFailedToPresent, requestId: requestId, reject: nil, errorData: errorData) + requestAd(requestId, unitId: unitId!, options: options!, resolve: nil, reject: nil) return } diff --git a/ios/Ads/FullScreen/RNAdMobFullScreenAd.swift b/ios/Ads/FullScreen/RNAdMobFullScreenAd.swift index 7463a0f..1d4e4a3 100644 --- a/ios/Ads/FullScreen/RNAdMobFullScreenAd.swift +++ b/ios/Ads/FullScreen/RNAdMobFullScreenAd.swift @@ -22,6 +22,35 @@ class RNAdMobFullScreenAd: NSObject { RNAdMobEvent.send(eventName, type: getAdType(), requestId: NSNumber(value: requestId), data: data) } + func sendError(eventName: String, requestId: Int, reject: RCTPromiseRejectBlock?, errorData: Dictionary) { + let error = NSError.init(domain: "com.rnadmob.admob", code: 0, userInfo: errorData) + if (reject != nil) { + var message = "" + if (eventName == kEventAdFailedToLoad) { + message = "Error occurred while loading ad." + } else { + message = "Error occurred while showing ad." + } + reject!(eventName, message, error) + } else if (eventName == kEventAdFailedToPresent) { + presentPromiseHolder.reject(requestId: requestId, errorData: errorData) + } + sendEvent(eventName: eventName, requestId: requestId, data: errorData) + } + + func createErrorData(code: Int?, message: String) -> Dictionary { + var error = Dictionary() + if (code != nil) { + error.updateValue(code!, forKey: "code") + } + error.updateValue(message, forKey: "message") + return error + } + + func createErrorData(error: Error) -> Dictionary { + return createErrorData(code: (error as NSError).code, message: error.localizedDescription) + } + func getViewController(reject: RCTPromiseRejectBlock?) -> UIViewController? { var viewController = RCTKeyWindow()?.rootViewController while true { @@ -35,9 +64,6 @@ class RNAdMobFullScreenAd: NSObject { break } } - if (viewController == nil && reject != nil) { - reject!("E_NIL_VC", "Cannot process Ad because the current View Controller is nil.", nil) - } return viewController } @@ -76,15 +102,8 @@ class RNAdMobFullScreenAd: NSObject { } } func onAdFailedToLoad(error: Error) { - if (reject != nil) { - let code = String.localizedStringWithFormat("E_AD_LOAD_FAILED(%d)", (error as NSError).code) - reject!(code, error.localizedDescription, error) - } - - var data = Dictionary() - data.updateValue((error as NSError).code, forKey: "code") - data.updateValue(error.localizedDescription, forKey: "message") - module.sendEvent(eventName: kEventAdFailedToLoad, requestId: requestId, data: data) + let errorData = module.createErrorData(error: error) + module.sendError(eventName: kEventAdFailedToLoad, requestId: requestId, reject: reject, errorData: errorData) if (module.getAdType() == RNAdMobAppOpen.AD_TYPE) { if (!RNAdMobAppOpen.appStarted) { @@ -110,13 +129,8 @@ class RNAdMobFullScreenAd: NSObject { module.sendEvent(eventName: kEventAdPresented, requestId: requestId, data: nil) } func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { - module.presentPromiseHolder.reject(requestId: requestId, error: error) - module.sendEvent(eventName: kEventAdFailedToPresent, requestId: requestId, data: nil) - - var data = Dictionary() - data.updateValue((error as NSError).code, forKey: "code") - data.updateValue(error.localizedDescription, forKey: "message") - module.sendEvent(eventName: kEventAdFailedToLoad, requestId: requestId, data: data) + let errorData = module.createErrorData(error: error) + module.sendError(eventName: kEventAdFailedToPresent, requestId: requestId, reject: nil, errorData: errorData) module.adHolder.remove(requestId: requestId) } @@ -155,6 +169,8 @@ class RNAdMobFullScreenAd: NSObject { DispatchQueue.main.async { [self] in let viewController = getViewController(reject: reject) if (viewController == nil) { + let errorData = createErrorData(code: nil, message: "Current view controller is nil.") + sendError(eventName: kEventAdFailedToPresent, requestId: requestId, reject: reject, errorData: errorData) return } @@ -165,18 +181,16 @@ class RNAdMobFullScreenAd: NSObject { } self.show(ad: ad!, viewController: viewController!, requestId: requestId) } else { - if (reject != nil) { - reject!("E_AD_NOT_READY", "Ad is not ready", nil) - } - var error = Dictionary() - error.updateValue("Ad is not ready", forKey: "message") - sendEvent(eventName: kEventAdFailedToPresent, requestId: requestId, data: error) + let errorData = createErrorData(code: nil, message: "Ad is not loaded.") + sendError(eventName: kEventAdFailedToPresent, requestId: requestId, reject: nil, errorData: errorData) } } } func destroyAd(_ requestId: Int) { adHolder.remove(requestId: requestId) - presentPromiseHolder.reject(requestId: requestId, code: "E_AD_DESTROYED", message: "Ad has been destroyed") + + let errorData = createErrorData(code: nil, message: "Ad has been destroyed.") + presentPromiseHolder.reject(requestId: requestId, errorData: errorData) } } diff --git a/ios/RNAdMobPromiseHolder.swift b/ios/RNAdMobPromiseHolder.swift index eade563..251570f 100644 --- a/ios/RNAdMobPromiseHolder.swift +++ b/ios/RNAdMobPromiseHolder.swift @@ -23,20 +23,11 @@ class RNAdMobPromiseHolder { rejectArray.removeValue(forKey: requestId) } - func reject(requestId: Int, error: Error) { + func reject(requestId: Int, errorData: Dictionary) { let rejectBlock = rejectArray[requestId] if (rejectBlock != nil) { - let code = String.localizedStringWithFormat("E_AD_PRESENT_FAILED(%d)", (error as NSError).code) - rejectBlock!(code, error.localizedDescription, error) - } - resolveArray.removeValue(forKey: requestId) - rejectArray.removeValue(forKey: requestId) - } - - func reject(requestId: Int, code: String, message: String) { - let rejectBlock = rejectArray[requestId] - if (rejectBlock != nil) { - rejectBlock!(code, message, nil) + let error = NSError.init(domain: "com.rnadmob.admob", code: 0, userInfo: errorData) + rejectBlock!(kEventAdFailedToPresent, "Error occurred while showing ad.", error) } resolveArray.removeValue(forKey: requestId) rejectArray.removeValue(forKey: requestId) diff --git a/src/AdError.ts b/src/AdError.ts new file mode 100644 index 0000000..bc07c8d --- /dev/null +++ b/src/AdError.ts @@ -0,0 +1,6 @@ +export default class AdError extends Error { + name = 'AdError'; + constructor(public readonly message: string, public readonly code?: number) { + super(); + } +} diff --git a/src/ads/fullscreen/FullScreenAd.ts b/src/ads/fullscreen/FullScreenAd.ts index b44c750..154d218 100644 --- a/src/ads/fullscreen/FullScreenAd.ts +++ b/src/ads/fullscreen/FullScreenAd.ts @@ -4,6 +4,7 @@ import { NativeModules, } from 'react-native'; +import AdError from '../../AdError'; import { AdType, AppOpenAdOptions, @@ -90,7 +91,12 @@ export default class FullScreenAd< addEventListener(event: E, handler: H) { const eventHandler = (e: Event) => { if (e.type === this.type && e.requestId === this.requestId) { - handler(e.data); + if (event === 'adFailedToLoad' || event === 'adFailedToPresent') { + // @ts-ignore + handler(new AdError(e.data.message, e.data.code)); + } else { + handler(e.data); + } } }; const listener = eventEmitter.addListener(event, eventHandler); @@ -118,21 +124,41 @@ export default class FullScreenAd< * Loads a new Ad. * @param requestOptions Optional RequestOptions used to load the ad. */ - load(requestOptions: RequestOptions = {}) { + async load(requestOptions: RequestOptions = {}) { const options = { ...this.options, ...({ requestOptions, } as FullScreenAdOptions | AppOpenAdOptions), }; - return this.nativeModule.requestAd(this.requestId, this.unitId, options); + try { + return this.nativeModule.requestAd(this.requestId, this.unitId, options); + } catch (error: any) { + if (error.code === 'adFailedToLoad') { + return Promise.reject( + new AdError(error.userInfo.message, error.userInfo.code) + ); + } else { + return Promise.reject(error); + } + } } /** * Shows loaded Ad. */ - show() { - return this.nativeModule.presentAd(this.requestId); + async show() { + try { + return this.nativeModule.presentAd(this.requestId); + } catch (error: any) { + if (error.code === 'adFailedToPresent') { + return Promise.reject( + new AdError(error.userInfo.message, error.userInfo.code) + ); + } else { + return Promise.reject(error); + } + } } /** diff --git a/src/hooks/useFullScreenAd.ts b/src/hooks/useFullScreenAd.ts index 6de9095..03bca31 100644 --- a/src/hooks/useFullScreenAd.ts +++ b/src/hooks/useFullScreenAd.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; +import AdError from '../AdError'; import { AppOpenAd, InterstitialAd, @@ -24,8 +25,8 @@ export default function useFullScreenAd< const [adLoaded, setAdLoaded] = useState(false); const [adPresented, setAdPresented] = useState(false); const [adDismissed, setAdDismissed] = useState(false); - const [adLoadError, setAdLoadError] = useState(); - const [adPresentError, setAdPresentError] = useState(); + const [adLoadError, setAdLoadError] = useState(); + const [adPresentError, setAdPresentError] = useState(); const [reward, setReward] = useState(); const adShowing = useMemo( @@ -67,13 +68,13 @@ export default function useFullScreenAd< ad.addEventListener('adLoaded', () => { setAdLoaded(true); }), - ad.addEventListener('adFailedToLoad', (error: Error) => + ad.addEventListener('adFailedToLoad', (error: AdError) => setAdLoadError(error) ), ad.addEventListener('adPresented', () => { setAdPresented(true); }), - ad.addEventListener('adFailedToPresent', (error: Error) => + ad.addEventListener('adFailedToPresent', (error: AdError) => setAdPresentError(error) ), ad.addEventListener('adDismissed', () => { diff --git a/src/index.ts b/src/index.ts index f9f0bba..e448d43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,3 +6,4 @@ export * from './constants'; export * from './hooks'; export * from './types'; +export { default as AdError } from './AdError'; diff --git a/src/types.ts b/src/types.ts index 99b2a2c..f9cfa50 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ import { ViewProps } from 'react-native'; import BannerAdSize from './constants/BannerAdSize'; +import AdError from './AdError'; export type MaxAdContentRating = 'G' | 'MA' | 'PG' | 'T'; @@ -191,7 +192,7 @@ export type FullScreenAdEvent = export type RewardedAdEvent = FullScreenAdEvent | 'rewarded'; -export type FullScreenAdHandlerType = (() => void) | ((error: Error) => void); +export type FullScreenAdHandlerType = (() => void) | ((error: AdError) => void); export type RewardedAdHandlerType = | FullScreenAdHandlerType @@ -242,11 +243,11 @@ export interface AdHookReturns { /** * Error during ad load. */ - adLoadError?: Error; + adLoadError?: AdError; /** * Error during ad present. */ - adPresentError?: Error; + adPresentError?: AdError; /** * Reward earned by Rewarded Ad. */