mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-01 22:41:41 +08:00
@@ -59,4 +59,5 @@ dependencies {
|
||||
compile "com.google.firebase:firebase-crash:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-config:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-perf:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-ads:$firebaseVersion"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.invertase.firebase">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
|
||||
@@ -9,10 +9,12 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.invertase.firebase.admob.RNFirebaseAdMobBanner;
|
||||
import io.invertase.firebase.auth.RNFirebaseAuth;
|
||||
import io.invertase.firebase.config.RNFirebaseRemoteConfig;
|
||||
import io.invertase.firebase.storage.RNFirebaseStorage;
|
||||
@@ -21,6 +23,7 @@ import io.invertase.firebase.analytics.RNFirebaseAnalytics;
|
||||
import io.invertase.firebase.crash.RNFirebaseCrash;
|
||||
import io.invertase.firebase.messaging.RNFirebaseMessaging;
|
||||
import io.invertase.firebase.perf.RNFirebasePerformance;
|
||||
import io.invertase.firebase.admob.RNFirebaseAdMob;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RNFirebasePackage implements ReactPackage {
|
||||
@@ -45,6 +48,7 @@ public class RNFirebasePackage implements ReactPackage {
|
||||
modules.add(new RNFirebaseCrash(reactContext));
|
||||
modules.add(new RNFirebaseRemoteConfig(reactContext));
|
||||
modules.add(new RNFirebasePerformance(reactContext));
|
||||
modules.add(new RNFirebaseAdMob(reactContext));
|
||||
return modules;
|
||||
}
|
||||
|
||||
@@ -66,6 +70,8 @@ public class RNFirebasePackage implements ReactPackage {
|
||||
*/
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
return Arrays.<ViewManager>asList(
|
||||
new RNFirebaseAdMobBanner()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package io.invertase.firebase.admob;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.google.android.gms.ads.AdRequest;
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class RNFirebaseAdMob extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "RNFirebaseAdmob";
|
||||
|
||||
ReactApplicationContext getContext() {
|
||||
return getReactApplicationContext();
|
||||
}
|
||||
|
||||
Activity getActivity() {
|
||||
return getCurrentActivity();
|
||||
}
|
||||
|
||||
private HashMap<String, RNFirebaseAdmobInterstitial> interstitials = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseRewardedVideo> rewardedVideos = new HashMap<>();
|
||||
|
||||
public RNFirebaseAdMob(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
Log.d(TAG, "New instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void interstitialLoadAd(String adUnit, ReadableMap request) {
|
||||
RNFirebaseAdmobInterstitial interstitial = getOrCreateInterstitial(adUnit);
|
||||
interstitial.loadAd(buildRequest(request).build());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void interstitialShowAd(String adUnit) {
|
||||
RNFirebaseAdmobInterstitial interstitial = getOrCreateInterstitial(adUnit);
|
||||
interstitial.show();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void rewardedVideoLoadAd(String adUnit, ReadableMap request) {
|
||||
RNFirebaseRewardedVideo rewardedVideo = getOrCreateRewardedVideo(adUnit);
|
||||
rewardedVideo.loadAd(buildRequest(request).build());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void rewardedVideoShowAd(String adUnit) {
|
||||
RNFirebaseRewardedVideo rewardedVideo = getOrCreateRewardedVideo(adUnit);
|
||||
rewardedVideo.show();
|
||||
}
|
||||
|
||||
private RNFirebaseAdmobInterstitial getOrCreateInterstitial(String adUnit) {
|
||||
if (interstitials.containsKey(adUnit)) {
|
||||
return interstitials.get(adUnit);
|
||||
}
|
||||
RNFirebaseAdmobInterstitial interstitial = new RNFirebaseAdmobInterstitial(adUnit, this);
|
||||
interstitials.put(adUnit, interstitial);
|
||||
return interstitial;
|
||||
}
|
||||
|
||||
private RNFirebaseRewardedVideo getOrCreateRewardedVideo(String adUnit) {
|
||||
if (rewardedVideos.containsKey(adUnit)) {
|
||||
return rewardedVideos.get(adUnit);
|
||||
}
|
||||
RNFirebaseRewardedVideo rewardedVideo = new RNFirebaseRewardedVideo(adUnit, this);
|
||||
rewardedVideos.put(adUnit, rewardedVideo);
|
||||
return rewardedVideo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
final Map<String, Object> constants = new HashMap<>();
|
||||
constants.put("DEVICE_ID_EMULATOR", AdRequest.DEVICE_ID_EMULATOR);
|
||||
return constants;
|
||||
}
|
||||
|
||||
AdRequest.Builder buildRequest(ReadableMap request) {
|
||||
AdRequest.Builder requestBuilder = new AdRequest.Builder();
|
||||
|
||||
if (request.hasKey("testDevice")) {
|
||||
requestBuilder.addTestDevice(AdRequest.DEVICE_ID_EMULATOR);
|
||||
}
|
||||
|
||||
ReadableArray keywords = request.getArray("keywords");
|
||||
List<Object> keywordsList = Utils.recursivelyDeconstructReadableArray(keywords);
|
||||
|
||||
for (Object word : keywordsList) {
|
||||
requestBuilder.addKeyword((String) word);
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package io.invertase.firebase.admob;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
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.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.view.ReactViewGroup;
|
||||
import com.google.android.gms.ads.AdListener;
|
||||
import com.google.android.gms.ads.AdRequest;
|
||||
import com.google.android.gms.ads.AdSize;
|
||||
import com.google.android.gms.ads.AdView;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class RNFirebaseAdMobBanner extends SimpleViewManager<ReactViewGroup> implements View.OnLayoutChangeListener {
|
||||
|
||||
public static final String REACT_CLASS = "RNFirebaseAdMobBanner";
|
||||
public static final String BANNER_EVENT = "bannerEvent";
|
||||
|
||||
public enum Events {
|
||||
EVENT_AD_SIZE_CHANGE("onSizeChange"),
|
||||
EVENT_AD_LOADED("onAdLoaded"),
|
||||
EVENT_AD_FAILED_TO_LOAD("onAdFailedToLoad"),
|
||||
EVENT_AD_OPENED("onAdOpened"),
|
||||
EVENT_AD_CLOSED("onAdClosed"),
|
||||
EVENT_AD_LEFT_APPLICATION("onAdLeftApplication");
|
||||
|
||||
private final String event;
|
||||
|
||||
Events(final String name) {
|
||||
event = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
private ThemedReactContext context;
|
||||
private ReactViewGroup viewGroup;
|
||||
private RCTEventEmitter emitter;
|
||||
private String size;
|
||||
private Boolean testing = false;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create & return view instance
|
||||
* @param themedReactContext
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public ReactViewGroup createViewInstance(ThemedReactContext themedReactContext) {
|
||||
context = themedReactContext;
|
||||
viewGroup = new ReactViewGroup(themedReactContext);
|
||||
emitter = themedReactContext.getJSModule(RCTEventEmitter.class);
|
||||
|
||||
attachAdViewToViewGroup();
|
||||
return viewGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare custom events
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
|
||||
builder.put(BANNER_EVENT, MapBuilder.of("registrationName", BANNER_EVENT));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the React View changes, reset the Ad size
|
||||
* @param view
|
||||
* @param left
|
||||
* @param top
|
||||
* @param right
|
||||
* @param bottom
|
||||
* @param oldLeft
|
||||
* @param oldTop
|
||||
* @param oldRight
|
||||
* @param oldBottom
|
||||
*/
|
||||
@Override
|
||||
public void onLayoutChange(View view, final int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
// If the view has changed at all, recalculate what banner we need
|
||||
if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) {
|
||||
setSize(viewGroup, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unitId prop
|
||||
* @param view
|
||||
* @param value
|
||||
*/
|
||||
@ReactProp(name = "unitId")
|
||||
public void setUnitId(final ReactViewGroup view, final String value) {
|
||||
AdView adViewView = (AdView) view.getChildAt(0);
|
||||
adViewView.setAdUnitId(value);
|
||||
requestAd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle testing prop
|
||||
* @param view
|
||||
* @param value
|
||||
*/
|
||||
@ReactProp(name = "testing")
|
||||
public void setUnitId(final ReactViewGroup view, final Boolean value) {
|
||||
testing = value;
|
||||
requestAd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle size prop
|
||||
* @param view
|
||||
* @param value
|
||||
*/
|
||||
@ReactProp(name = "size")
|
||||
public void setSize(final ReactViewGroup view, final @Nullable String value) {
|
||||
if (value != null) {
|
||||
size = value;
|
||||
}
|
||||
|
||||
AdSize adSize = propToAdSize(size.toUpperCase());
|
||||
AdView adViewView = (AdView) view.getChildAt(0);
|
||||
adViewView.setAdSize(adSize);
|
||||
|
||||
// Send the width & height back to the JS
|
||||
int width;
|
||||
int height;
|
||||
WritableMap payload = Arguments.createMap();
|
||||
|
||||
if (adSize == AdSize.SMART_BANNER) {
|
||||
width = (int) PixelUtil.toDIPFromPixel(adSize.getWidthInPixels(context));
|
||||
height = (int) PixelUtil.toDIPFromPixel(adSize.getHeightInPixels(context));
|
||||
} else {
|
||||
width = adSize.getWidth();
|
||||
height = adSize.getHeight();
|
||||
}
|
||||
|
||||
payload.putDouble("width", width);
|
||||
payload.putDouble("height", height);
|
||||
|
||||
sendEvent(Events.EVENT_AD_SIZE_CHANGE.toString(), payload);
|
||||
requestAd();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance of the AdView and attaches it to the
|
||||
* current ReactViewGroup
|
||||
*/
|
||||
void attachAdViewToViewGroup() {
|
||||
removeAdFromViewGroup();
|
||||
|
||||
final AdView adView = new AdView(context);
|
||||
viewGroup.addView(adView);
|
||||
setAdListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the AdView from the ViewGroup
|
||||
*/
|
||||
void removeAdFromViewGroup() {
|
||||
AdView adView = (AdView) viewGroup.getChildAt(0);
|
||||
viewGroup.removeAllViews();
|
||||
|
||||
if (adView != null) {
|
||||
adView.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a new ad into a viewGroup
|
||||
*/
|
||||
void requestAd() {
|
||||
AdView adView = (AdView) viewGroup.getChildAt(0);
|
||||
|
||||
if (adView.getAdSize() == null || adView.getAdUnitId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AdRequest.Builder adRequestBuilder = new AdRequest.Builder();
|
||||
|
||||
// If the prop testing is set, assign the emulators device ID
|
||||
if (testing) {
|
||||
adRequestBuilder.addTestDevice(AdRequest.DEVICE_ID_EMULATOR);
|
||||
}
|
||||
|
||||
AdRequest adRequest = adRequestBuilder.build();
|
||||
adView.loadAd(adRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to Ad events
|
||||
*/
|
||||
void setAdListener() {
|
||||
final AdView adView = (AdView) viewGroup.getChildAt(0);
|
||||
|
||||
adView.setAdListener(new AdListener() {
|
||||
@Override
|
||||
public void onAdLoaded() {
|
||||
int left = adView.getLeft();
|
||||
int top = adView.getTop();
|
||||
|
||||
int width = adView.getAdSize().getWidthInPixels(context);
|
||||
int height = adView.getAdSize().getHeightInPixels(context);
|
||||
|
||||
adView.measure(width, height);
|
||||
adView.layout(left, top, left + width, top + height);
|
||||
|
||||
sendEvent(Events.EVENT_AD_LOADED.toString(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdFailedToLoad(int errorCode) {
|
||||
WritableMap payload = RNFirebaseAdMobUtils.errorCodeToMap(errorCode);
|
||||
sendEvent(Events.EVENT_AD_FAILED_TO_LOAD.toString(), payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdOpened() {
|
||||
sendEvent(Events.EVENT_AD_OPENED.toString(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdClosed() {
|
||||
sendEvent(Events.EVENT_AD_CLOSED.toString(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdLeftApplication() {
|
||||
sendEvent(Events.EVENT_AD_LEFT_APPLICATION.toString(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an event back to the JS component to handle
|
||||
* @param type
|
||||
* @param payload
|
||||
*/
|
||||
void sendEvent(String type, final @Nullable WritableMap payload) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putString("type", type);
|
||||
|
||||
if (payload != null) {
|
||||
event.putMap("payload", payload);
|
||||
}
|
||||
|
||||
emitter.receiveEvent(viewGroup.getId(), BANNER_EVENT, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the size prop to the AdSize
|
||||
* @param prop
|
||||
* @return
|
||||
*/
|
||||
AdSize propToAdSize(String prop) {
|
||||
switch (prop) {
|
||||
default:
|
||||
case "BANNER":
|
||||
return AdSize.BANNER;
|
||||
case "LARGE_BANNER":
|
||||
return AdSize.LARGE_BANNER;
|
||||
case "MEDIUM_RECTANGLE":
|
||||
return AdSize.MEDIUM_RECTANGLE;
|
||||
case "FULL_BANNER":
|
||||
return AdSize.FULL_BANNER;
|
||||
case "LEADERBOARD":
|
||||
return AdSize.LEADERBOARD;
|
||||
case "SMART_BANNER":
|
||||
return AdSize.SMART_BANNER;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.invertase.firebase.admob;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.ads.AdRequest;
|
||||
|
||||
class RNFirebaseAdMobUtils {
|
||||
|
||||
/**
|
||||
* Convert common AdMob errors into a standard format
|
||||
* @param errorCode
|
||||
* @return
|
||||
*/
|
||||
static WritableMap errorCodeToMap(int errorCode) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
|
||||
switch (errorCode) {
|
||||
case AdRequest.ERROR_CODE_INTERNAL_ERROR:
|
||||
map.putString("code", "admob/error-code-internal-error");
|
||||
map.putString("message", "Something happened internally; for instance, an invalid response was received from the ad server.");
|
||||
break;
|
||||
case AdRequest.ERROR_CODE_INVALID_REQUEST:
|
||||
map.putString("code", "admob/error-code-invalid-request");
|
||||
map.putString("message", "The ad request was invalid; for instance, the ad unit ID was incorrect.");
|
||||
break;
|
||||
case AdRequest.ERROR_CODE_NETWORK_ERROR:
|
||||
map.putString("code", "admob/error-code-network-error");
|
||||
map.putString("message", "The ad request was unsuccessful due to network connectivity.");
|
||||
break;
|
||||
case AdRequest.ERROR_CODE_NO_FILL:
|
||||
map.putString("code", "admob/error-code-no-fill");
|
||||
map.putString("message", "The ad request was successful, but no ad was returned due to lack of ad inventory.");
|
||||
break;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package io.invertase.firebase.admob;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.ads.AdListener;
|
||||
import com.google.android.gms.ads.AdRequest;
|
||||
import com.google.android.gms.ads.InterstitialAd;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
class RNFirebaseAdmobInterstitial {
|
||||
|
||||
private InterstitialAd interstitialAd;
|
||||
private RNFirebaseAdMob adMob;
|
||||
private AdListener adListener;
|
||||
private String adUnit;
|
||||
|
||||
RNFirebaseAdmobInterstitial(final String adUnitString, final RNFirebaseAdMob adMobInstance) {
|
||||
adUnit = adUnitString;
|
||||
adMob = adMobInstance;
|
||||
interstitialAd = new InterstitialAd(adMob.getContext());
|
||||
interstitialAd.setAdUnitId(adUnit);
|
||||
|
||||
adListener = new AdListener() {
|
||||
@Override
|
||||
public void onAdLoaded() {
|
||||
sendEvent("onAdLoaded", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdOpened() {
|
||||
sendEvent("onAdOpened", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdLeftApplication() {
|
||||
sendEvent("onAdLeftApplication", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdClosed() {
|
||||
sendEvent("onAdClosed", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdFailedToLoad(int errorCode) {
|
||||
WritableMap payload = RNFirebaseAdMobUtils.errorCodeToMap(errorCode);
|
||||
sendEvent("onAdFailedToLoad", payload);
|
||||
}
|
||||
};
|
||||
|
||||
interstitialAd.setAdListener(adListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an Ad with a AdRequest instance
|
||||
* @param adRequest
|
||||
*/
|
||||
void loadAd(final AdRequest adRequest) {
|
||||
Activity activity = adMob.getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
interstitialAd.loadAd(adRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the loaded interstitial, if it's loaded
|
||||
*/
|
||||
void show() {
|
||||
Activity activity = adMob.getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interstitialAd.isLoaded()) {
|
||||
interstitialAd.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a native event over the bridge with a type and optional payload
|
||||
* @param type
|
||||
* @param payload
|
||||
*/
|
||||
void sendEvent(String type, final @Nullable WritableMap payload) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("type", type);
|
||||
map.putString("adunit", adUnit);
|
||||
|
||||
if (payload != null) {
|
||||
map.putMap("payload", payload);
|
||||
}
|
||||
|
||||
Utils.sendEvent(adMob.getContext(), "interstitial_event", map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package io.invertase.firebase.admob;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.ads.AdListener;
|
||||
import com.google.android.gms.ads.AdRequest;
|
||||
import com.google.android.gms.ads.InterstitialAd;
|
||||
import com.google.android.gms.ads.MobileAds;
|
||||
import com.google.android.gms.ads.reward.RewardItem;
|
||||
import com.google.android.gms.ads.reward.RewardedVideoAd;
|
||||
import com.google.android.gms.ads.reward.RewardedVideoAdListener;
|
||||
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
public class RNFirebaseRewardedVideo implements RewardedVideoAdListener {
|
||||
|
||||
private RewardedVideoAd mAd;
|
||||
private String adUnit;
|
||||
private RNFirebaseAdMob adMob;
|
||||
private RewardedVideoAd rewardedVideo;
|
||||
|
||||
RNFirebaseRewardedVideo(final String adUnitString, final RNFirebaseAdMob adMobInstance) {
|
||||
adUnit = adUnitString;
|
||||
adMob = adMobInstance;
|
||||
|
||||
rewardedVideo = MobileAds.getRewardedVideoAdInstance(adMob.getContext());
|
||||
rewardedVideo.setRewardedVideoAdListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an Ad with a AdRequest instance
|
||||
* @param adRequest
|
||||
*/
|
||||
void loadAd(final AdRequest adRequest) {
|
||||
Activity activity = adMob.getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
rewardedVideo.loadAd(adUnit, adRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the loaded interstitial, if it's loaded
|
||||
*/
|
||||
void show() {
|
||||
Activity activity = adMob.getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (rewardedVideo.isLoaded()) {
|
||||
rewardedVideo.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewarded(RewardItem reward) {
|
||||
sendEvent("onRewarded", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewardedVideoAdLeftApplication() {
|
||||
sendEvent("onRewardedVideoAdLeftApplication", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewardedVideoAdClosed() {
|
||||
sendEvent("onRewardedVideoAdClosed", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewardedVideoAdFailedToLoad(int errorCode) {
|
||||
WritableMap payload = RNFirebaseAdMobUtils.errorCodeToMap(errorCode);
|
||||
sendEvent("onRewardedVideoAdFailedToLoad", payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewardedVideoAdLoaded() {
|
||||
sendEvent("onRewardedVideoAdLoaded", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewardedVideoAdOpened() {
|
||||
sendEvent("onRewardedVideoAdOpened", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewardedVideoStarted() {
|
||||
sendEvent("onRewardedVideoStarted", null);
|
||||
}
|
||||
|
||||
// TODO onResume etc??? https://developers.google.com/admob/android/rewarded-video
|
||||
|
||||
/**
|
||||
* Send a native event over the bridge with a type and optional payload
|
||||
* @param type
|
||||
* @param payload
|
||||
*/
|
||||
void sendEvent(String type, final @Nullable WritableMap payload) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("type", type);
|
||||
map.putString("adunit", adUnit);
|
||||
|
||||
if (payload != null) {
|
||||
map.putMap("payload", payload);
|
||||
}
|
||||
|
||||
Utils.sendEvent(adMob.getContext(), "rewarded_video_event", map);
|
||||
}
|
||||
}
|
||||
69
docs/modules/admob.md
Normal file
69
docs/modules/admob.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# AdMob
|
||||
|
||||
The admob allows you to display adverts in your app, using your account from [AdMob by Google](https://www.google.co.uk/admob/).
|
||||
|
||||
RNFirebase allows you to display Banners, Interstitials, Native Ads & Rewarded Videos.
|
||||
|
||||
### Banner
|
||||
|
||||
AdMob Banners in RNFirebase are exported as a usable React component, allowing you to integrate it easily into your existing app very easily.
|
||||
|
||||
```js
|
||||
const Banner = firebase.admob.Banner;
|
||||
...
|
||||
render() {
|
||||
return (
|
||||
<Banner
|
||||
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Interstitial
|
||||
|
||||
An interstitial is a full screen advert which creates a new activity on top of React. As they need to be controlled,
|
||||
allowing the developer to choose when to display them they're not available as a component. Instead they're controlled via
|
||||
method calls.
|
||||
|
||||
To request an interstitial from AdMob, the `loadAd` method must be called with an instance of `AdRequest` (see below for full API):
|
||||
|
||||
```js
|
||||
const advert = firebase.admob().interstitial('ca-app-pub-3940256099942544/1033173712');
|
||||
|
||||
const AdRequest = firebase.admob.AdRequest;
|
||||
const request = new AdRequest();
|
||||
request.addKeyword('foo').addKeyword('bar');
|
||||
|
||||
// Load the advert with our AdRequest
|
||||
advert.loadAd(request.build());
|
||||
|
||||
// Simulate the interstitial being shown "sometime" later during the apps lifecycle
|
||||
setTimeout(() => {
|
||||
if (advert.isLoaded()) {
|
||||
advert.show();
|
||||
} else {
|
||||
// Unable to show interstitial - not loaded yet.
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
```
|
||||
|
||||
### Native
|
||||
|
||||
### Rewarded Video
|
||||
|
||||
## Statics
|
||||
|
||||
### Banner
|
||||
> Accessed via `firebase.admob.Banner`.
|
||||
|
||||
Exports a React component with the following PropTypes:
|
||||
|
||||
|
||||
### AdRequest
|
||||
> Accessed via `firebase.admob.AdRequest`.
|
||||
|
||||
Used to build a request object to pass into AdMob requests. Exposes the following chainable methods:
|
||||
|
||||
2
index.js
2
index.js
@@ -1,3 +1,5 @@
|
||||
import Firebase from './lib/firebase';
|
||||
|
||||
export const AdMob = require('./lib/modules/admob');
|
||||
|
||||
export default Firebase;
|
||||
|
||||
@@ -16,6 +16,7 @@ import Analytics from './modules/analytics';
|
||||
import Crash from './modules/crash';
|
||||
import RemoteConfig from './modules/config';
|
||||
import Performance from './modules/perf';
|
||||
import AdMob, { statics as AdMobStatics } from './modules/admob';
|
||||
|
||||
const instances: Object = { default: null };
|
||||
const FirebaseModule = NativeModules.RNFirebase;
|
||||
@@ -36,6 +37,7 @@ export default class Firebase {
|
||||
_config: ?Object;
|
||||
_crash: ?Object;
|
||||
_perf: ?Object;
|
||||
_admob: ?Object;
|
||||
|
||||
auth: Function;
|
||||
crash: Function;
|
||||
@@ -45,6 +47,7 @@ export default class Firebase {
|
||||
messaging: Function;
|
||||
config: Function;
|
||||
perf: Function;
|
||||
admob: Function;
|
||||
|
||||
eventHandlers: Object;
|
||||
debug: boolean;
|
||||
@@ -90,6 +93,7 @@ export default class Firebase {
|
||||
this.crash = this._staticsOrInstance('crash', {}, Crash);
|
||||
this.config = this._staticsOrInstance('config', {}, RemoteConfig);
|
||||
this.perf = this._staticsOrInstance('perf', {}, Performance);
|
||||
this.admob = this._staticsOrInstance('admob', AdMobStatics, AdMob);
|
||||
|
||||
// init auth to start listeners
|
||||
this.auth();
|
||||
|
||||
22
lib/modules/admob/AdRequest.js
Normal file
22
lib/modules/admob/AdRequest.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export default class AdRequest {
|
||||
|
||||
constructor() {
|
||||
this._props = {
|
||||
keywords: [],
|
||||
};
|
||||
}
|
||||
|
||||
build() {
|
||||
return this._props;
|
||||
}
|
||||
|
||||
addTestDevice() {
|
||||
this._props.testDevice = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
addKeyword(word: string) {
|
||||
this._props.keywords.push(word);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
82
lib/modules/admob/Banner.js
Normal file
82
lib/modules/admob/Banner.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { requireNativeComponent, View } from 'react-native';
|
||||
import { statics } from './';
|
||||
import { nativeToJSError } from '../../utils';
|
||||
|
||||
class Banner extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
...View.propTypes,
|
||||
// TODO ehesp: cant init this outside of the component; statics isn't defined
|
||||
...(() => {
|
||||
const eventProps = {};
|
||||
Object.keys(statics.EventTypes).forEach((key) => {
|
||||
eventProps[key] = PropTypes.func;
|
||||
});
|
||||
return eventProps;
|
||||
}),
|
||||
size: PropTypes.string,
|
||||
unitId: PropTypes.string,
|
||||
testing: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
size: 'SMART_BANNER',
|
||||
unitId: 'ca-app-pub-3940256099942544/6300978111', // Testing
|
||||
testing: true,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a single banner event and pass to
|
||||
* any props watching it
|
||||
* @param nativeEvent
|
||||
*/
|
||||
onBannerEvent = ({ nativeEvent }) => {
|
||||
if (this.props[nativeEvent.type]) {
|
||||
if (nativeEvent.type === 'onAdFailedToLoad') {
|
||||
const { code, message } = nativeEvent.payload;
|
||||
this.props[nativeEvent.type](nativeToJSError(code, message));
|
||||
} else {
|
||||
this.props[nativeEvent.type](nativeEvent.payload || {});
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeEvent.type === 'onSizeChange') this.updateSize(nativeEvent.payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a native onSizeChange event
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
updateSize = ({ width, height }) => {
|
||||
this.setState({ width, height });
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the native component
|
||||
* @returns {XML}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<RNFirebaseAdMobBanner
|
||||
{...this.props}
|
||||
style={[this.props.style, { ...this.state }]}
|
||||
bannerEvent={this.onBannerEvent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const RNFirebaseAdMobBanner = requireNativeComponent('RNFirebaseAdMobBanner', Banner);
|
||||
|
||||
export default Banner;
|
||||
82
lib/modules/admob/Interstitial.js
Normal file
82
lib/modules/admob/Interstitial.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
import { statics } from './';
|
||||
import { nativeToJSError } from '../../utils';
|
||||
|
||||
const FirebaseAdMob = NativeModules.RNFirebaseAdmob;
|
||||
|
||||
export default class Interstitial {
|
||||
|
||||
constructor(admob: Object, adunit: string) {
|
||||
this.admob = admob;
|
||||
this.adUnit = adunit;
|
||||
this.loaded = false;
|
||||
this.admob.on(`interstitial_${adunit}`, this._onInterstitialEvent.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a JS emit event
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onInterstitialEvent(event) {
|
||||
const eventType = `interstitial:${this.adUnit}:${event.type}`;
|
||||
|
||||
let emitData = Object.assign({}, event);
|
||||
|
||||
switch (event.type) {
|
||||
case 'onAdLoaded':
|
||||
this.loaded = true;
|
||||
break;
|
||||
case 'onAdFailedToLoad':
|
||||
emitData = nativeToJSError(event.payload.code, event.payload.message);
|
||||
emitData.type = event.type;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
this.admob.emit(eventType, emitData);
|
||||
this.admob.emit(`interstitial:${this.adUnit}:*`, emitData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an ad with an instance of AdRequest
|
||||
* @param request
|
||||
* @returns {*}
|
||||
*/
|
||||
loadAd(request: AdRequest) {
|
||||
return FirebaseAdMob.interstitialLoadAd(this.adUnit, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a local instance of isLoaded
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the advert - will only show if loaded
|
||||
* @returns {*}
|
||||
*/
|
||||
show() {
|
||||
if (this.loaded) {
|
||||
FirebaseAdMob.interstitialShowAd(this.adUnit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an Ad event
|
||||
* @param eventType
|
||||
* @param listenerCb
|
||||
* @returns {null}
|
||||
*/
|
||||
on(eventType, listenerCb) {
|
||||
if (!statics.EventTypes[eventType]) {
|
||||
console.warn(`Invalid event type provided, must be one of: ${Object.keys(statics.EventTypes).join(', ')}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.admob.on(`interstitial:${this.adUnit}:${eventType}`, listenerCb);
|
||||
}
|
||||
}
|
||||
56
lib/modules/admob/index.js
Normal file
56
lib/modules/admob/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||
import Interstitial from './Interstitial';
|
||||
import AdRequest from './AdRequest';
|
||||
import Banner from './Banner';
|
||||
import { Base } from './../base';
|
||||
|
||||
const FirebaseAdMob = NativeModules.RNFirebaseAdMob;
|
||||
const FirebaseAdMobEvt = new NativeEventEmitter(FirebaseAdMob);
|
||||
|
||||
export default class Admob extends Base {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
FirebaseAdMobEvt.addListener('interstitial_event', this._onInterstitialEvent.bind(this));
|
||||
}
|
||||
|
||||
_onInterstitialEvent(event) {
|
||||
const { adunit } = event;
|
||||
const jsEventType = `interstitial_${adunit}`;
|
||||
|
||||
if (!this.hasListeners(jsEventType)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
this.emit(jsEventType, event);
|
||||
}
|
||||
|
||||
interstitial(adUnit: string) {
|
||||
return new Interstitial(this, adUnit);
|
||||
}
|
||||
|
||||
static get statics() {
|
||||
return statics;
|
||||
}
|
||||
}
|
||||
|
||||
export const statics = {
|
||||
Banner,
|
||||
AdRequest,
|
||||
EventTypes: {
|
||||
onAdLoaded: 'onAdLoaded',
|
||||
onAdOpened: 'onAdOpened',
|
||||
onAdLeftApplication: 'onAdLeftApplication',
|
||||
onAdClosed: 'onAdClosed',
|
||||
onAdFailedToLoad: 'onAdFailedToLoad',
|
||||
},
|
||||
RewardedEventTypes: {
|
||||
onRewarded: 'onRewarded',
|
||||
onRewardedVideoAdLeftApplication: 'onRewardedVideoAdLeftApplication',
|
||||
onRewardedVideoAdClosed: 'onRewardedVideoAdClosed',
|
||||
onRewardedVideoAdFailedToLoad: 'onRewardedVideoAdFailedToLoad',
|
||||
onRewardedVideoAdLoaded: 'onRewardedVideoAdLoaded',
|
||||
onRewardedVideoAdOpened: 'onRewardedVideoAdOpened',
|
||||
onRewardedVideoStarted: 'onRewardedVideoStarted',
|
||||
},
|
||||
};
|
||||
@@ -1,25 +1,14 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import Log from '../utils/log';
|
||||
import EventEmitter from './../utils/eventEmitter';
|
||||
|
||||
const FirebaseModule = NativeModules.RNFirebase;
|
||||
const FirebaseModuleEvt = new NativeEventEmitter(FirebaseModule);
|
||||
|
||||
const logs = {};
|
||||
const SharedEventEmitter = new EventEmitter();
|
||||
|
||||
type FirebaseOptions = {};
|
||||
|
||||
export class Base extends EventEmitter {
|
||||
constructor(firebase: Object, options: FirebaseOptions = {}) {
|
||||
super();
|
||||
this.firebase = firebase;
|
||||
this.eventHandlers = {};
|
||||
this.options = Object.assign({}, firebase.options, options);
|
||||
}
|
||||
export class Base {
|
||||
|
||||
/**
|
||||
* Return a namespaced instance of Log
|
||||
@@ -27,52 +16,53 @@ export class Base extends EventEmitter {
|
||||
*/
|
||||
get log(): Log {
|
||||
if (logs[this.namespace]) return logs[this.namespace];
|
||||
return logs[this.namespace] = new Log(this.namespace, this.firebase._debug);
|
||||
|
||||
// todo grab log level from global config provider (still todo);
|
||||
return logs[this.namespace] = new Log(this.namespace, '*');
|
||||
}
|
||||
|
||||
/**
|
||||
* app instance
|
||||
**/
|
||||
get app(): Object {
|
||||
return this.firebase.app;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a native module event subscription
|
||||
* @param name
|
||||
* @param handler
|
||||
* @param nativeModule
|
||||
* @returns {*}
|
||||
* @private
|
||||
/*
|
||||
* Proxy functions to shared event emitter instance
|
||||
* https://github.com/facebook/react-native/blob/master/Libraries/EventEmitter/EventEmitter.js
|
||||
*/
|
||||
_on(name, handler, nativeModule) {
|
||||
let _nativeModule = nativeModule;
|
||||
|
||||
if (!_nativeModule) {
|
||||
_nativeModule = FirebaseModuleEvt;
|
||||
}
|
||||
|
||||
return this.eventHandlers[name] = _nativeModule.addListener(name, handler);
|
||||
get sharedEventEmitter () {
|
||||
return SharedEventEmitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a native module event subscription
|
||||
* @param name
|
||||
* @private
|
||||
*/
|
||||
_off(name): void {
|
||||
const subscription = this.eventHandlers[name];
|
||||
if (!subscription) return;
|
||||
get addListener() {
|
||||
return SharedEventEmitter.addListener.bind(SharedEventEmitter);
|
||||
}
|
||||
|
||||
subscription.remove();
|
||||
delete this.eventHandlers[name];
|
||||
get on() {
|
||||
return SharedEventEmitter.addListener.bind(SharedEventEmitter);
|
||||
}
|
||||
|
||||
get emit() {
|
||||
return SharedEventEmitter.emit.bind(SharedEventEmitter);
|
||||
}
|
||||
|
||||
get listeners() {
|
||||
return SharedEventEmitter.listeners.bind(SharedEventEmitter);
|
||||
}
|
||||
|
||||
hasListeners(eventType: string): Boolean {
|
||||
const subscriptions = SharedEventEmitter._subscriber.getSubscriptionsForType(eventType);
|
||||
return subscriptions && subscriptions.length;
|
||||
}
|
||||
|
||||
get removeListener() {
|
||||
return SharedEventEmitter.removeListener.bind(SharedEventEmitter);
|
||||
}
|
||||
|
||||
get removeAllListeners() {
|
||||
return SharedEventEmitter.removeAllListeners.bind(SharedEventEmitter);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReferenceBase extends Base {
|
||||
constructor(firebase: Object, path: string) {
|
||||
super(firebase);
|
||||
constructor(path: string) {
|
||||
super();
|
||||
this.path = path || '/';
|
||||
}
|
||||
|
||||
|
||||
@@ -326,3 +326,9 @@ export function generatePushID(serverTimeOffset?: number = 0): string {
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export function nativeToJSError(code: string, message: string) {
|
||||
const error = new Error(message);
|
||||
error.code = code;
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { Banner } from './../firebase/modules/admob';
|
||||
|
||||
import CoreContainer from './containers/CoreContainer';
|
||||
import setupStore from './store/setup';
|
||||
import { setupSuites } from './tests/index';
|
||||
@@ -50,11 +52,12 @@ function bootstrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider store={this.state.store}>
|
||||
<CoreContainer />
|
||||
</Provider>
|
||||
);
|
||||
return <Banner style={{ width: 100, height: 100, backgroundColor: 'pink'}} />;
|
||||
// return (
|
||||
// <Provider store={this.state.store}>
|
||||
// <CoreContainer />
|
||||
// </Provider>
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user