# BigCrunch Mobile Ads SDK for React Native

Simplified in-app advertising for React Native apps with S2S demand and Google Ad Manager integration.

## Features

- **Simple Integration**: One SDK to handle S2S demand, Google Ad Manager, and Amazon integration
- **Built-in Analytics**: Automatic tracking of screen views, impressions, clicks, and revenue
- **Revenue Optimization**: Server-side header bidding via BigCrunch S2S
- **Configuration-Driven**: Manage placements and ad sizes from the BigCrunch dashboard
- **Cross-Platform**: Supports both iOS and Android
- **Ad Formats**: Banner, Interstitial, and Rewarded ads

## Requirements

- React Native 0.60+
- iOS 13.0+
- Android API 21+
- **Bare React Native** project (not Expo managed workflow)
  - If using Expo, you must first run `expo prebuild` to generate native directories

## Installation

```bash
npm install @bigcrunch/react-native-ads
# or
yarn add @bigcrunch/react-native-ads
```

### iOS Setup

```bash
cd ios && pod install && cd ..
```

Add the following to your `Info.plist`:

```xml
<key>GADApplicationIdentifier</key>
<string>YOUR_GAM_APP_ID</string>
```

> For testing, you can use the Google test ID: `ca-app-pub-3940256099942544~1458002511`

### Android Setup

Add the following inside the `<application>` tag in your `AndroidManifest.xml`:

```xml
<meta-data
    android:name="com.google.android.gms.ads.APPLICATION_ID"
    android:value="YOUR_GAM_APP_ID"/>
```

> For testing, you can use: `ca-app-pub-3940256099942544~3347511713`

## Quick Start

### 1. Initialize the SDK

```typescript
import { useEffect } from 'react';
import { BigCrunchAds } from '@bigcrunch/react-native-ads';

const PROPERTY_ID = 'YOUR_PROPERTY_ID';

function App() {
  useEffect(() => {
    initializeBigCrunch();
  }, []);

  const initializeBigCrunch = async () => {
    try {
      await BigCrunchAds.initialize(PROPERTY_ID, 'production');
      console.log('BigCrunch SDK initialized');
    } catch (error) {
      console.error('BigCrunch initialization failed:', error);
    }
  };

  return (
    // Your app content
  );
}
```

| Parameter | Description |
|-----------|-------------|
| `PROPERTY_ID` | Unique identifier for your app, provided by BigCrunch |
| `environment` | `'production'` for live apps, `'sandbox'` for testing |

### 2. Track Screen Views

```typescript
import { useEffect } from 'react';
import { BigCrunchAds } from '@bigcrunch/react-native-ads';

function HomeScreen() {
  useEffect(() => {
    BigCrunchAds.trackScreenView('HomeScreen');
  }, []);

  return (
    // Screen content
  );
}
```

> For automatic tracking with React Navigation, see the [Tracking-Only Integration Guide](./docs/TRACKING_ONLY_INTEGRATION.md#option-b-automatic-tracking-with-react-navigation-recommended).

## Ad Formats

### Banner Ads

Ad sizes are configured per placement in the BigCrunch dashboard. You only need to pass the `placementId` — the SDK handles the rest.

```tsx
import { BigCrunchBannerView } from '@bigcrunch/react-native-ads';

function ArticleScreen() {
  return (
    <View style={{ flex: 1 }}>
      <Text>Article content...</Text>

      <BigCrunchBannerView
        placementId="your-banner-placement"
        onAdLoaded={() => console.log('Banner loaded')}
        onAdFailedToLoad={(error) => console.error('Banner failed:', error.message)}
      />
    </View>
  );
}
```

#### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `placementId` | `string` | *required* | Placement ID from the BigCrunch dashboard |
| `size` | `BannerSize \| { width, height }` | `'BANNER'` | Banner size. Defaults to standard 320x50. |
| `autoLoad` | `boolean` | `true` | Automatically load ad on mount |
| `refreshInterval` | `number` | `0` | Auto-refresh interval in seconds (0 = disabled) |
| `customTargeting` | `Record<string, string>` | — | Custom key-value targeting |
| `style` | `ViewStyle` | — | Container style |

#### Callbacks

| Callback | Description |
|----------|-------------|
| `onAdLoaded` | Ad loaded successfully |
| `onAdFailedToLoad` | Ad failed to load. Receives `AdError` with `code` and `message`. |
| `onAdImpression` | Ad impression recorded |
| `onAdClicked` | User tapped the ad |
| `onAdOpened` | Ad opened an overlay |
| `onAdClosed` | Ad overlay closed |
| `onAdRevenue` | Revenue event received (ILRD). Receives `AdRevenue` with `valueMicros` and `currencyCode`. |

#### Available Banner Sizes

| Size | Dimensions | Description |
|------|-----------|-------------|
| `'BANNER'` | 320x50 | Standard banner |
| `'LARGE_BANNER'` | 320x100 | Large banner |
| `'MEDIUM_RECTANGLE'` | 300x250 | Medium rectangle (MREC) |
| `'FULL_BANNER'` | 468x60 | Full-size banner |
| `'LEADERBOARD'` | 728x90 | Leaderboard (tablets) |
| `'ADAPTIVE'` | Full width, auto height | Adaptive banner (recommended) |

### Interstitial Ads

Full-screen ads shown at natural transition points. Preload ahead of time, then show when ready.

```typescript
import { useEffect, useRef } from 'react';
import { BigCrunchInterstitial } from '@bigcrunch/react-native-ads';
import type { EventSubscription } from '@bigcrunch/react-native-ads';

const PLACEMENT_ID = 'interstitial-level-complete';

function GameScreen() {
  const subscriptions = useRef<EventSubscription[]>([]);

  useEffect(() => {
    // Preload on mount
    BigCrunchInterstitial.load({ placementId: PLACEMENT_ID });

    // Set up listeners
    subscriptions.current.push(
      BigCrunchInterstitial.addEventListener(PLACEMENT_ID, 'adClosed', () => {
        // Preload the next interstitial
        BigCrunchInterstitial.load({ placementId: PLACEMENT_ID });
      }),
      BigCrunchInterstitial.addEventListener(PLACEMENT_ID, 'adFailedToLoad', (event) => {
        console.error('Interstitial failed:', event.error.message);
      })
    );

    return () => {
      subscriptions.current.forEach((sub) => sub.remove());
      BigCrunchInterstitial.destroy(PLACEMENT_ID);
    };
  }, []);

  const handleLevelComplete = async () => {
    if (await BigCrunchInterstitial.isLoaded(PLACEMENT_ID)) {
      await BigCrunchInterstitial.show(PLACEMENT_ID);
    }
  };

  return (
    <Button title="Complete Level" onPress={handleLevelComplete} />
  );
}
```

#### Methods

| Method | Description |
|--------|-------------|
| `load(options)` | Load an interstitial ad |
| `show(placementId)` | Show a loaded interstitial |
| `isLoaded(placementId)` | Check if an ad is loaded |
| `destroy(placementId)` | Destroy an interstitial instance |
| `addEventListener(placementId, event, listener)` | Add an event listener |

#### Events

| Event | Description |
|-------|-------------|
| `'adLoaded'` | Ad loaded and ready to show |
| `'adFailedToLoad'` | Loading failed |
| `'adImpression'` | Impression recorded |
| `'adClicked'` | User tapped the ad |
| `'adOpened'` | Full-screen ad presented |
| `'adClosed'` | User dismissed the ad |
| `'adRevenue'` | Revenue event (ILRD) |

### Rewarded Ads

Full-screen ads that offer the user an in-app reward in exchange for watching. The API is identical to interstitials, with the addition of the `rewardEarned` event.

```typescript
import { useEffect, useRef, useState } from 'react';
import { Button, Alert } from 'react-native';
import { BigCrunchRewarded } from '@bigcrunch/react-native-ads';
import type { EventSubscription } from '@bigcrunch/react-native-ads';

const PLACEMENT_ID = 'rewarded-extra-life';

function GameOverScreen() {
  const [adReady, setAdReady] = useState(false);
  const subscriptions = useRef<EventSubscription[]>([]);

  useEffect(() => {
    BigCrunchRewarded.load({ placementId: PLACEMENT_ID });

    subscriptions.current.push(
      BigCrunchRewarded.addEventListener(PLACEMENT_ID, 'adLoaded', () => {
        setAdReady(true);
      }),
      BigCrunchRewarded.addEventListener(PLACEMENT_ID, 'adClosed', () => {
        setAdReady(false);
        BigCrunchRewarded.load({ placementId: PLACEMENT_ID });
      }),
      BigCrunchRewarded.addEventListener(PLACEMENT_ID, 'rewardEarned', (event) => {
        Alert.alert('Reward!', `You earned ${event.rewardAmount} ${event.rewardType}`);
      })
    );

    return () => {
      subscriptions.current.forEach((sub) => sub.remove());
      BigCrunchRewarded.destroy(PLACEMENT_ID);
    };
  }, []);

  const handleWatchAd = async () => {
    if (await BigCrunchRewarded.isLoaded(PLACEMENT_ID)) {
      await BigCrunchRewarded.show(PLACEMENT_ID);
    }
  };

  return (
    <Button title="Watch Ad for Extra Life" onPress={handleWatchAd} disabled={!adReady} />
  );
}
```

#### Additional Events

All interstitial events plus:

| Event | Description |
|-------|-------------|
| `'rewardEarned'` | User completed the ad and earned a reward. Includes `rewardType` (string) and `rewardAmount` (number). |

## Privacy & Compliance

```typescript
// GDPR — pass IAB TCF consent string
await BigCrunchAds.setGdprConsent('consent-string-here');

// CCPA — pass IAB US Privacy string
await BigCrunchAds.setCcpaString('1YNN');

// COPPA — for apps directed at children
await BigCrunchAds.setCoppaCompliant(true);
```

## Verifying Your Integration

```typescript
// Check if SDK is initialized
const isReady = await BigCrunchAds.isInitialized();
console.log('SDK initialized:', isReady);

// Get session information
const session = await BigCrunchAds.getSessionInfo();
console.log('Session ID:', session.sessionId);
console.log('Session depth:', session.sessionDepth);

// Enable debug logging during development
await BigCrunchAds.setDebugMode(true);

// Register test devices to receive test ads
await BigCrunchAds.addTestDevice('YOUR_DEVICE_ID');
```

> **Finding your device ID:** Run your app with debug mode enabled, load an ad, and check the console output. The Google Mobile Ads SDK will log your device ID.

## Error Codes

When an ad fails to load, the `AdError` object contains a `code` and `message`:

| Code | Description |
|------|-------------|
| `NETWORK_ERROR` | No network connectivity |
| `NO_FILL` | No ad available for this request |
| `INVALID_REQUEST` | Malformed ad request |
| `INTERNAL_ERROR` | Internal SDK error |
| `INVALID_PLACEMENT` | Placement ID not found in config |
| `NOT_INITIALIZED` | SDK not yet initialized |
| `ALREADY_LOADED` | An ad is already loaded for this placement |
| `NOT_LOADED` | Attempted to show an ad that isn't loaded |
| `TIMEOUT` | Ad request timed out |

## TypeScript Support

The SDK includes full TypeScript definitions:

```typescript
import type {
  AdError,
  AdRevenue,
  BannerSize,
  CustomSize,
  PlacementConfig,
  AppConfig,
  SessionInfo,
  EventSubscription,
  AdErrorCode,
} from '@bigcrunch/react-native-ads';
```

## Full Documentation

- [Full Integration Guide (Tracking + Ads)](./docs/FULL_INTEGRATION.md)
- [Tracking-Only Integration Guide](./docs/TRACKING_ONLY_INTEGRATION.md)
- [Google Ad Manager Setup](./docs/GOOGLE_SETUP.md)
- [Testing Guide](./docs/TESTING_GUIDE.md)
- [Example App](./example)

## Troubleshooting

### iOS: Pod install fails
```bash
cd ios
pod deintegrate
pod cache clean --all
pod install
```

### Android: Build fails with duplicate classes
Add to `android/app/build.gradle`:
```gradle
android {
    packagingOptions {
        pickFirst 'META-INF/*'
    }
}
```

### Ads not loading ("No Fill")
- Normal during testing — ad inventory varies by region and time
- Register your device as a test device to receive test ads
- Verify placement IDs match the BigCrunch dashboard
- Check the GAM App ID in `Info.plist` / `AndroidManifest.xml`

### Interstitial/Rewarded not showing
- Call `load()` before `show()`
- Check `isLoaded()` before calling `show()`
- Listen for `adFailedToLoad` events to diagnose issues

## Support

- Documentation: https://docs.bigcrunch.com/mobile-ads-sdk
- Issues: https://github.com/bigcrunch/mobile-ads-sdk/issues
- Email: support@bigcrunch.com

## License

MIT
