diff --git a/example/index.js b/example/index.js index 980f090..cf99dfe 100644 --- a/example/index.js +++ b/example/index.js @@ -1,7 +1,7 @@ /** * @format */ -import {AppRegistry} from 'react-native'; +import {AppRegistry, DeviceEventEmitter} from 'react-native'; import {AdManager, TestIds} from 'react-native-admob-native-ads'; import App from './App'; import {name as appName} from './app.json'; @@ -55,4 +55,8 @@ AdManager.registerRepository({ console.log('registered: ', result); }); +AdManager.subscribe('imageAd', 'onAdPreloadClicked', () => { + console.log('click', 'imageAd'); +}); + AppRegistry.registerComponent(appName, () => App); diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index bc0c7b4..a8a9699 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -280,7 +280,7 @@ PODS: - React-jsinspector (0.71.11) - React-logger (0.71.11): - glog - - react-native-admob-native-ads (0.6.1): + - react-native-admob-native-ads (0.6.4): - Google-Mobile-Ads-SDK - React-Core - react-native-tracking-transparency (0.1.2): @@ -521,7 +521,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 089cd07c76ecf498960a64ba8ae0f2dddd382f44 React-jsinspector: b6ed4cb3ffa27a041cd440300503dc512b761450 React-logger: 186dd536128ae5924bc38ed70932c00aa740cd5b - react-native-admob-native-ads: f39c004b2903d344059f052e3d53308694cbae02 + react-native-admob-native-ads: 3df173d98678bad9072a7576b09ac86259e816cb react-native-tracking-transparency: 25ff1ff866e338c137c818bdec20526bb05ffcc1 React-perflogger: e706562ab7eb8eb590aa83a224d26fa13963d7f2 React-RCTActionSheet: 57d4bd98122f557479a3359ad5dad8e109e20c5a diff --git a/example/package-lock.json b/example/package-lock.json index bf942e7..008fe10 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -25,7 +25,7 @@ "babel-jest": "^29.2.1", "eslint": "^8.19.0", "jest": "^29.2.1", - "metro-react-native-babel-preset": "0.73.8", + "metro-react-native-babel-preset": "0.73.10", "prettier": "^2.4.1", "react-test-renderer": "18.2.0", "typescript": "4.8.4" @@ -11463,10 +11463,9 @@ } }, "node_modules/metro-react-native-babel-preset": { - "version": "0.73.8", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.8.tgz", - "integrity": "sha512-spNrcQJTbQntEIqJnCA6yL4S+dzV9fXCk7U+Rm7yJasZ4o4Frn7jP23isu7FlZIp1Azx1+6SbP7SgQM+IP5JgQ==", - "dev": true, + "version": "0.73.10", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", + "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", "dependencies": { "@babel/core": "^7.20.0", "@babel/plugin-proposal-async-generator-functions": "^7.0.0", @@ -11528,54 +11527,6 @@ "@babel/core": "*" } }, - "node_modules/metro-react-native-babel-transformer/node_modules/metro-react-native-babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", - "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, "node_modules/metro-resolver": { "version": "0.73.10", "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.73.10.tgz", @@ -11768,54 +11719,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/metro/node_modules/metro-react-native-babel-preset": { - "version": "0.73.10", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.10.tgz", - "integrity": "sha512-1/dnH4EHwFb2RKEKx34vVDpUS3urt2WEeR8FYim+ogqALg4sTpG7yeQPxWpbgKATezt4rNfqAANpIyH19MS4BQ==", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, "node_modules/metro/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/example/src/AdView.js b/example/src/AdView.js index 1551949..8d697e8 100644 --- a/example/src/AdView.js +++ b/example/src/AdView.js @@ -73,78 +73,13 @@ export const AdView = React.memo(({index, media, type, loadOnMount = true}) => { Logger('AD', 'LEFT', 'Ad left application'); }; - const onViewableItemsChanged = useCallback( - event => { - /** - * [STEP IV] We check if any AdViews are currently viewable. - */ - let viewableAds = event.viewableItems.filter( - i => i.key.indexOf('ad') !== -1, - ); - - viewableAds.forEach(adView => { - if (adView.index === index && !loaded) { - /** - * [STEP V] If the ad is viewable and not loaded - * already, we will load the ad. - */ - setLoading(true); - setLoaded(false); - setError(false); - Logger('AD', 'IN VIEW', 'Loading ' + index); - nativeAdRef.current?.loadAd(); - } else { - /** - * We will not reload ads or load - * ads that are not viewable currently - * to save bandwidth and requests sent - * to server. - */ - if (loaded) { - Logger('AD', 'IN VIEW', 'Loaded ' + index); - } else { - Logger('AD', 'NOT IN VIEW', index); - } - } - }); - }, - [index, loaded], - ); - useEffect(() => { - /** - * for previous steps go to List.js file. - * - * [STEP III] We will subscribe to onViewableItemsChanged event in all AdViews in the List. - */ - let onViewableItemsChangedHandler; - - if (!loadOnMount) { - onViewableItemsChangedHandler = DeviceEventEmitter.addListener( - Events.onViewableItemsChanged, - onViewableItemsChanged, - ); - } - - return () => { - if (!loadOnMount) { - onViewableItemsChangedHandler.remove(); - } - }; - }, [index, loadOnMount, loaded, onViewableItemsChanged]); - - useEffect(() => { - if (loadOnMount || index <= 15) { - setLoading(true); - setLoaded(false); - setError(false); + if (!loaded) { nativeAdRef.current?.loadAd(); + } else { + Logger('AD', 'LOADED ALREADY'); } - return () => { - setLoaded(false); - }; - }, [loadOnMount, index]); - + }, [loaded]); return ( { onNativeAdLoaded={onNativeAdLoaded} refreshInterval={60000 * 2} style={{ - width: '98%', + width: '100%', alignSelf: 'center', - backgroundColor: 'transparent', }} videoOptions={{ customControlsRequested: true, @@ -184,7 +118,7 @@ export const AdView = React.memo(({index, media, type, loadOnMount = true}) => { opacity: !loading && !error && loaded ? 0 : 1, zIndex: !loading && !error && loaded ? 0 : 10, }}> - {loading && } + {!loading && } {error && :-(} @@ -193,10 +127,10 @@ export const AdView = React.memo(({index, media, type, loadOnMount = true}) => { height: 100, width: '100%', flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'flex-start', + alignItems: 'center', paddingHorizontal: 10, opacity: loading || error || !loaded ? 0 : 1, + maxWidth: '100%', }}> { /> { const renderItem = React.useCallback( @@ -20,7 +20,7 @@ const List = () => { * loadOnMount -> We are telling the AdView to not load the ad when * it is mounted. */ - + ) : ( { borderBottomColor: 'orange', width: '100%', paddingHorizontal: 12, - }} - > + }}> + }}> {item} @@ -59,15 +57,6 @@ const List = () => { ); }, []); - /** - * [STEP I] When viewable items change in the list - * we want to know what items are visible and store them - * in a variable for later us. - */ - const onViewableItemsChanged = React.useCallback(e => { - viewableItemsChanged = e; - }, []); - const keyExtractor = React.useCallback(item => item, []); return ( @@ -79,7 +68,6 @@ const List = () => { onScrollAnimationEnd={onScrollEnd} onMomentumScrollEnd={onScrollEnd} onScrollEndDrag={onScrollEnd} - onViewableItemsChanged={onViewableItemsChanged} renderItem={renderItem} /> @@ -91,6 +79,7 @@ export default List; const styles = StyleSheet.create({ list: { flex: 1, + width: '100%', }, container: { height: '100%', diff --git a/index.d.ts b/index.d.ts index f09e7a2..155f442 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,6 +5,7 @@ import { ImageProps, TextStyle, StyleProp, + EmitterSubscription, } from "react-native"; type Image = { @@ -107,10 +108,10 @@ type VideoOptions = { clickToExpand?: boolean; /** Set this to true if you want to use custom controls for play/pause etc on videos */ customControlsRequested?: boolean; -} +}; type MediationOptions = { nativeBanner?: boolean; -} +}; type TargetingOptions = { targets?: Array<{ key: boolean; value: string | Array }>; @@ -143,18 +144,18 @@ type AdRepositoryConfig = { /**The number of ads to preload. Default is `5` */ numOfAds?: number; /** -* Under the Google EU User Consent Policy, you must make certain disclosures -* to your users in the European Economic Area (EEA) and obtain their consent -* to use cookies or other local storage, where legally required, and to use -* personal data (such as AdID) to serve ads. This policy reflects the requirements -* of the EU ePrivacy Directive and the General Data Protection Regulation (GDPR). -* -* You can use library such as: https://github.com/birgernass/react-native-ad-consent -* to obtain the consent or if you are using rn-firebase you can obtain the consent from -* there and then pass the consent to this library. If user has selected -* non-personalized-ads then pass `true` and non-personalized ads will be shown to the user. -* -*/ + * Under the Google EU User Consent Policy, you must make certain disclosures + * to your users in the European Economic Area (EEA) and obtain their consent + * to use cookies or other local storage, where legally required, and to use + * personal data (such as AdID) to serve ads. This policy reflects the requirements + * of the EU ePrivacy Directive and the General Data Protection Regulation (GDPR). + * + * You can use library such as: https://github.com/birgernass/react-native-ad-consent + * to obtain the consent or if you are using rn-firebase you can obtain the consent from + * there and then pass the consent to this library. If user has selected + * non-personalized-ads then pass `true` and non-personalized ads will be shown to the user. + * + */ requestNonPersonalizedAdsOnly?: boolean; /** After how long should ads in this repository expire after being loaded in milliseconds. Default is `3600000`.*/ expirationPeriod?: number; @@ -163,9 +164,9 @@ type AdRepositoryConfig = { videoOptions?: VideoOptions; mediationOptions?: MediationOptions; targetingOptions?: TargetingOptions; - adChoicesPlacement?: "topLeft" | "topRight" | "bottomLeft" | "bottomRight" - mediaAspectRatio?: "any" | "landscape" | "portrait" | "square" | "unknown" -} + adChoicesPlacement?: "topLeft" | "topRight" | "bottomLeft" | "bottomRight"; + mediaAspectRatio?: "any" | "landscape" | "portrait" | "square" | "unknown"; +}; type ImagePropsWithOptionalSource = Omit & Partial>; @@ -196,11 +197,11 @@ type NativeAdViewProps = { mediaAspectRatio?: "any" | "landscape" | "portrait" | "square" | "unknown"; /** - * A repository is used to preload ads before they are presented. - * Provide the name of the repository registered for ad caching. - * If you have not registered a repository, you can do so by + * A repository is used to preload ads before they are presented. + * Provide the name of the repository registered for ad caching. + * If you have not registered a repository, you can do so by * calling `AdManager.registerRepository`. - * + * * **Note:** Use this only if you have registered a repository. */ @@ -292,17 +293,17 @@ type StarViewProps = { style?: StyleProp; size?: number; iconSet?: - | "Entypo" - | "EvilIcons" - | "Feather" - | "FontAwesome" - | "Foundation" - | "Ionicons" - | "MaterialIcons" - | "MaterialCommunityIcons" - | "Octicons" - | "Zocial" - | "SimpleLineIcons"; + | "Entypo" + | "EvilIcons" + | "Feather" + | "FontAwesome" + | "Foundation" + | "Ionicons" + | "MaterialIcons" + | "MaterialCommunityIcons" + | "Octicons" + | "Zocial" + | "SimpleLineIcons"; fullIcon?: string; halfIcon?: string; emptyIcon?: string; @@ -312,8 +313,6 @@ type StarViewProps = { }; declare module "react-native-admob-native-ads" { - - /** * * Wrapper for the UnifiedNativeAdView from Google Ads SDK. All your views should be @@ -356,7 +355,9 @@ declare module "react-native-admob-native-ads" { * */ - setRequestConfiguration: (config: Partial) => Promise>; + setRequestConfiguration: ( + config: Partial + ) => Promise>; /** * Check if the current device is registered as a test device to show test ads. @@ -365,7 +366,7 @@ declare module "react-native-admob-native-ads" { ``` return: `boolean` */ - isTestDevice: () => Promise + isTestDevice: () => Promise; /** * Register a repository with given settings for native ads @@ -373,7 +374,6 @@ declare module "react-native-admob-native-ads" { registerRepository: (config: AdRepositoryConfig) => Promise; - /** * Unregister a repository. All preloaded ads in this repository will be destroyed. */ @@ -388,11 +388,32 @@ declare module "react-native-admob-native-ads" { * Check if there is ad in a repository. */ hasAd: (name: string) => Promise; + /** + * When using Repositories to load ads, events are not recieved on the ad itself + * on android. If you want to track clicks and impressions you can do this using below + * events for each repository on android. + * + * Android only. + * @param repo + * @param eventName + * @param listener + * @returns + */ + subscribe: ( + repo: string, + eventName: + | "onAdPreloadLoaded" + | "onAdPreloadError" + | "onAdPreloadOpen" + | "onAdPreloadClosed" + | "onAdPreloadClicked" + | "onAdPreloadImpression", + listener + ) => EmitterSubscription; }; export const AdOptions: options; - /** * Ad Badge shows the {ad} badge on top of the ad telling the user that this is an AD. * @@ -472,5 +493,5 @@ declare module "react-native-admob-native-ads" { export const TestIds: { Video: string; Image: string; - } + }; } diff --git a/package.json b/package.json index 9a88887..627c7de 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-admob-native-ads", - "version": "0.6.4", + "version": "0.6.6", "description": "A simple and robust library for creating & displaying Admob Native Ads in your React Native App using Native Views", "author": "Ammar Ahmed ", "main": "index.js",