Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 64 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# React Native Meta Ads

A React Native package for Meta Audience Network integration.
Modern React Native package for Meta Audience Network integration built with TurboModules and the new architecture.
Supports:
- Interstitial ads
- Rewarded ads

**Built with React Native's new architecture featuring TurboModules, Codegen, and modern performance optimizations.**

## Development Status

⚠️ **Note**: This package currently supports Android only, which meets my requirements. iOS support is planned but I'm currently caught up with other projects and will return to it later.
Expand All @@ -23,20 +25,78 @@ npm install react-native-meta-ads
yarn add react-native-meta-ads
```

## Requirements

### Android

For optimal performance of video ads (including rewarded ads), it's recommended to enable hardware acceleration in your app's `AndroidManifest.xml`. While hardware acceleration is enabled by default for API level >= 14, explicitly enabling it ensures the best experience:

```xml
<application android:hardwareAccelerated="true" ...>
```

## API

### AdSettings

- `initialize(): Promise<void>`

### InterstitialAdManager

- `loadAd(placementId: string): Promise<void>`
- `showAd(placementId: string): Promise<void>`
- `onInterstitialDismissed: EventEmitter<void>`

### RewardedAdManager

- `loadAd(placementId: string): Promise<void>`
- `showAd(placementId: string): Promise<void>`
- `onRewardedVideoCompleted: EventEmitter<void>`

## Usage

Initialize the SDK at the top level of your app (e.g., in `App.js`):

```javascript
import { InterstitialAdManager, RewardedAdManager, AdSettings } from 'react-native-meta-ads';
import { useEffect, useRef } from 'react';
import { EventSubscription } from 'react-native';

// Initialize the SDK - in App.js (call conditionally based on user preferences, e.g., skip for premium users)
await AdSettings.initialize();
```

**Note:** The SDK is initialized as a method rather than automatically to give you control over when ads are loaded. This allows you to skip initialization for premium users (no ad) or implement conditional ad loading based on your app's business logic.

## Interstitial Ads

```javascript
// Interstitial ads with timer (see example folder for complete implementation)
useEffect(() => {
const subscription = InterstitialAdManager.onInterstitialDismissed(() => {
// Show next interstitial ad after 5 minutes
setTimeout(() => {
loadAndShowInterstitialAd()
}, 5 * 60 * 1000);
});

return () => subscription.remove();
}, []);

// Single function to load and show ad - you can instead load at app start and just show when needed (But be aware of ad invalidation and expiry)
const loadAndShowInterstitialAd = async () => {
try {
await InterstitialAdManager.loadAd(PLACEMENT_ID); // loads the ad
await InterstitialAdManager.showAd(PLACEMENT_ID); // shows the ad
} catch (error) {
console.error('Error with interstitial ad:', error);
}
}
```

// Basic ad loading and showing
await InterstitialAdManager.loadAd(PLACEMENT_ID);
await InterstitialAdManager.showAd(PLACEMENT_ID);
## Rewarded Ads

```javascript
// Rewarded ads with reward handling
function YourComponent() {
const [reward, setReward] = useState(0);
Expand Down Expand Up @@ -74,36 +134,6 @@ function YourComponent() {
}
```

## Requirements

### Android

For optimal performance of video ads (including rewarded ads), it's recommended to enable hardware acceleration in your app's `AndroidManifest.xml`. While hardware acceleration is enabled by default for API level >= 14, explicitly enabling it ensures the best experience:

```xml
<application android:hardwareAccelerated="true" ...>
```

## API

### AdSettings

- `initialize(): Promise<void>`
- `addTestDevice(deviceHash: string): void`
- `clearTestDevices(): void`
- `getCurrentDeviceHash(): string | undefined`

### InterstitialAdManager

- `loadAd(placementId: string): Promise<void>`
- `showAd(placementId: string): Promise<void>`

### RewardedAdManager

- `loadAd(placementId: string): Promise<void>`
- `showAd(placementId: string): Promise<void>`
- `onRewardedVideoCompleted: EventEmitter<void>`

## Test Device Handling

### ⚠️ Important: Policy Compliance
Expand Down Expand Up @@ -149,8 +179,6 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the

MIT

---

## ☕ Show Appreciation

If this package has helped your project or led to successful monetization, and you'd like to show appreciation, donations via [PayPal](https://paypal.me/akashp96) are welcome.
1 change: 1 addition & 0 deletions android/src/main/java/com/metaads/InterstitialAdManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class InterstitialAdManager(reactContext: ReactApplicationContext) : NativeInter

override fun onInterstitialDismissed(ad: com.facebook.ads.Ad) {
Log.d(TAG, "Interstitial ad dismissed")
emitOnInterstitialDismissed()
}

override fun onError(ad: com.facebook.ads.Ad, adError: AdError) {
Expand Down
55 changes: 43 additions & 12 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function App(): React.JSX.Element {
const [isRewardedLoaded, setIsRewardedLoaded] = useState(false);
const [reward, setReward] = useState(0);
const listenerSubscription = useRef<null | EventSubscription>(null);
const interstitialSubscription = useRef<null | EventSubscription>(null);

useEffect(() => {
// Initialize the sdk
Expand Down Expand Up @@ -67,6 +68,28 @@ function App(): React.JSX.Element {
manageTestDevices();
}, []);

// Interstitial ad dismissed listener with 5-minute timer
useEffect(() => {
interstitialSubscription.current =
InterstitialAdManager.onInterstitialDismissed(() => {
console.log('Interstitial ad dismissed, starting 5-minute timer');
// Load next ad after 5 minutes (or load immediately for preloading strategy)
// Note: Ads expire after ~1 hour, plan accordingly
setTimeout(
() => {
loadInterstitialAd();
},
10 * 1000 // 5 minutes - adjust for testing
);
});

return () => {
interstitialSubscription.current?.remove();
interstitialSubscription.current = null;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Rewarded ad completed listener
useEffect(() => {
listenerSubscription.current = RewardedAdManager.onRewardedVideoCompleted(
Expand Down Expand Up @@ -97,12 +120,15 @@ function App(): React.JSX.Element {
};

const loadInterstitialAd = async () => {
try {
await InterstitialAdManager.loadAd(INTERSTITIAL_PLACEMENT_ID);
setIsInterstitialLoaded(true);
console.log('Interstitial ad loaded successfully');
} catch (error) {
console.error('Failed to load interstitial ad:', error);
if (!isInterstitialLoaded) {
try {
await InterstitialAdManager.loadAd(INTERSTITIAL_PLACEMENT_ID);
setIsInterstitialLoaded(true);
console.log('Interstitial ad loaded successfully');
} catch (error) {
setIsInterstitialLoaded(false);
console.error('Failed to load interstitial ad:', error);
}
}
};

Expand All @@ -112,17 +138,21 @@ function App(): React.JSX.Element {
setIsInterstitialLoaded(false);
console.log('Interstitial ad shown successfully');
} catch (error) {
setIsInterstitialLoaded(false);
console.error('Error showing interstitial ad:', error);
}
};

const loadRewardedAd = async () => {
try {
await RewardedAdManager.loadAd(REWARDED_PLACEMENT_ID);
setIsRewardedLoaded(true);
console.log('Rewarded ad loaded successfully');
} catch (error) {
console.error('Failed to load rewarded ad:', error);
if (!isRewardedLoaded) {
try {
await RewardedAdManager.loadAd(REWARDED_PLACEMENT_ID);
setIsRewardedLoaded(true);
console.log('Rewarded ad loaded successfully');
} catch (error) {
setIsRewardedLoaded(false);
console.error('Failed to load rewarded ad:', error);
}
}
};

Expand All @@ -132,6 +162,7 @@ function App(): React.JSX.Element {
setIsRewardedLoaded(false);
console.log('Rewarded ad shown successfully');
} catch (error) {
setIsRewardedLoaded(false);
console.error('Error showing rewarded ad:', error);
}
};
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-meta-ads",
"version": "0.4.0",
"version": "0.5.0",
"description": "React native Meta Audience Network integration",
"source": "./src/index.tsx",
"main": "./lib/module/index.js",
Expand Down Expand Up @@ -45,6 +45,7 @@
"meta",
"facebook",
"fb",
"fbads",
"audience-network",
"audience",
"network",
Expand Down
8 changes: 8 additions & 0 deletions src/InterstitialAdManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ export const InterstitialAdManager = {
throw new Error('InterstitialAdManager not available');
}
},

onInterstitialDismissed: (() => {
if (NativeInterstitialAdManager) {
return NativeInterstitialAdManager.onInterstitialDismissed;
} else {
throw new Error('InterstitialAdManager not available');
}
})(),
};
2 changes: 2 additions & 0 deletions src/specs/NativeInterstitialAdManager.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';

export interface Spec extends TurboModule {
loadAd(placementId: string): Promise<void>;
showAd(placementId: string): Promise<void>;
readonly onInterstitialDismissed: EventEmitter<void>;
}

export default TurboModuleRegistry.get<Spec>('InterstitialAdManager');
Loading