Banner Ads
Banner ads are rectangular ads that occupy a portion of your app's layout. They can refresh automatically and stay on screen while users interact with your app.
Each ad format uses a Zone ID to identify the ad placement. Zone IDs are configured in the Empower dashboard.
Note: All ad status callbacks are optional. The SDK handles ad loading and display automatically. Use callbacks only if you need to track ad states for analytics, UI updates, or custom logic.
Supported Sizes
| Size | Description |
|---|---|
| 320x50 | Standard Banner |
| 320x100 | Large Banner |
| 300x250 | Medium Rectangle (MREC) |
Quick Start
import { EmpowerBannerAd } from '@empower-nokta/react-native-mobile-ads';
function HomeScreen() {
return (
<View style={{ flex: 1 }}>
<Text>Your Content</Text>
{/* Standard Banner */}
<EmpowerBannerAd
zoneId="YOUR_BANNER_ZONE_ID"
style={{ width: 320, height: 50 }}
/>
</View>
);
}
Zone ID Options
Zone IDs can be specified in three ways:
Single Zone ID (Same for Both Platforms)
<EmpowerBannerAd
zoneId="12345"
style={{ width: 320, height: 50 }}
/>
Per-Platform Props
<EmpowerBannerAd
androidZoneId="ANDROID_ZONE_ID"
iosZoneId="IOS_ZONE_ID"
style={{ width: 320, height: 50 }}
/>
Per-Platform Object
<EmpowerBannerAd
zoneId={{ android: 'ANDROID_ZONE_ID', ios: 'IOS_ZONE_ID' }}
style={{ width: 320, height: 50 }}
/>
Listening to Banner Status (Optional)
Track banner status for analytics or UI updates:
import { EmpowerBannerAd, AdStatus } from '@empower-nokta/react-native-mobile-ads';
function BannerWithStatus() {
const handleStatusChange = (event) => {
const { status, zoneId } = event.nativeEvent;
switch (status) {
case AdStatus.READY:
console.log(`Banner loaded for zone: ${zoneId}`);
break;
case AdStatus.FAILED:
console.log(`Banner failed for zone: ${zoneId}`);
break;
case AdStatus.WILL_LEAVE:
console.log('User clicked the ad');
break;
}
};
return (
<EmpowerBannerAd
zoneId="YOUR_ZONE_ID"
onAdStatusChanged={handleStatusChange}
style={{ width: 320, height: 50 }}
/>
);
}
Banners in Lists (FlatList / FlashList)
In-feed MRECs are the most common banner placement — and the easiest to get wrong. The key
is that the banner row is its own data item, identified by the item's type (never by a
hard-coded index — inserting or removing rows shifts indexes and a content row would render
in the banner slot).
FlatList
import { FlatList, View } from 'react-native';
import { EmpowerBannerAd } from '@empower-nokta/react-native-mobile-ads';
// Insert an ad item into the data, then branch on item.type in renderItem.
function withAds(items) {
const out = [];
items.forEach((item, i) => {
out.push({ type: 'content', id: item.id, data: item });
if ((i + 1) % 6 === 0) out.push({ type: 'ad', id: `ad-${i}` }); // every 6 rows
});
return out;
}
function FeedWithBanner({ items }) {
const renderItem = ({ item }) => {
if (item.type === 'ad') {
return (
<View style={{ alignItems: 'center', marginVertical: 8, height: 250 }}>
<EmpowerBannerAd
iosZoneId="IOS_MREC_ZONE_ID"
keepAlive /* iOS: reuse on scroll-back instead of reload */
style={{ width: 300, height: 250 }}
/>
</View>
);
}
return <FeedItem item={item.data} />;
};
return (
<FlatList
data={withAds(items)}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
);
}
FlashList (recycling list)
@shopify/flash-list recycles row views, which — without care — makes in-feed banners
tear down and reload on every scroll (blank flashes, wasted ad requests) and can even show a
stale ad from another zone in a recycled view. On iOS, configure it like this:
import { FlashList } from '@shopify/flash-list';
import { EmpowerBannerAd } from '@empower-nokta/react-native-mobile-ads';
<FlashList
data={withAds(items)}
keyExtractor={(item) => item.id}
renderItem={({ item }) =>
item.type === 'ad' ? (
<View style={{ alignItems: 'center', marginVertical: 8, height: 250 }}>
<EmpowerBannerAd
iosZoneId={item.zoneId}
keepAlive /* pause on scroll-off, reuse on scroll-back */
style={{ width: 300, height: 250 }}
/>
</View>
) : (
<FeedItem item={item.data} />
)
}
// Distinct recycle pool per ad zone → an ad view is never recycled into a different zone
getItemType={(item) => (item.type === 'ad' ? `ad:${item.zoneId}` : 'content')}
estimatedItemSize={160}
drawDistance={1000} // keep off-screen ads mounted a bit longer
/>
The three things that make it work (iOS):
keepAliveon every in-feed MREC — scrolling off-screen / unmounting pauses the zone instead of destroying it, so scrolling back reuses the loaded ad instantly (no new request, no flash).getItemTypereturning a distinct type per ad zone — so a recycled ad view is never handed a different zone (which would show a stale ad).drawDistanceraised (~800–1000) so MRECs aren't unmounted the instant they leave the viewport.
Note:
keepAlive,getItemTypeanddrawDistancematter on iOS. On Android the native SDK handles list recycling internally, sokeepAliveis a no-op there.
Sticky Banner
To display a sticky banner pinned to the bottom of the screen (above the tab bar):
import { View, StyleSheet } from 'react-native';
import { useSafeAreaInsets, SafeAreaProvider } from 'react-native-safe-area-context';
import { EmpowerBannerAd } from '@empower-nokta/react-native-mobile-ads';
function StickyBanner() {
const insets = useSafeAreaInsets();
return (
<View style={[styles.stickyBanner, { paddingBottom: insets.bottom }]}>
<EmpowerBannerAd
zoneId="YOUR_ZONE_ID"
keepAlive /* iOS: persistent slot — pause across screens, don't reload */
style={{ width: 320, height: 50 }}
/>
</View>
);
}
export default function App() {
return (
<SafeAreaProvider>
<View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
{/* Your app content / navigator */}
</View>
<StickyBanner />
</View>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
stickyBanner: {
backgroundColor: '#fff',
alignItems: 'center',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: '#ddd',
},
});
Tip: Place the
StickyBannercomponent outside your navigation container so it persists across all screens.
Multi-Size, Adaptive and Native Zones (iOS)
Some zones fill at a size you cannot know ahead of time: a multi-size zone (for example 320x50 or 320x100), an adaptive zone, or a zone with native ads. With a fixed height the ad is either cropped or leaves empty space. Add autoHeight and the banner takes the height of the ad on display:
<EmpowerBannerAd
zoneId="YOUR_ZONE_ID"
autoHeight
style={{ width: '100%', height: 50 }} /* used until the first ad arrives */
/>
- The height in
styleis used until the first ad arrives. After that, the banner resizes whenever the ad's size changes, for example when a refresh fills a different size. - Without
autoHeightthe banner keeps the size you give it, as before.
To size the slot yourself, use onAdSizeChanged:
<EmpowerBannerAd
zoneId="YOUR_ZONE_ID"
onAdSizeChanged={(e) => {
const { width, height, zoneId } = e.nativeEvent;
setSlotHeight(height);
}}
style={{ width: '100%', height: slotHeight }}
/>
Custom Targeting
Pass key-value targeting with customParameters. It is sent with every request of the banner.
<EmpowerBannerAd
zoneId="YOUR_ZONE_ID"
customParameters="category=sports&tags=football,league"
style={{ width: 320, height: 50 }}
/>
- Separate pairs with
&, and separate multiple values of one key with,. - Keys may contain letters, digits and
_, up to 20 characters. On iOS, a pair whose key breaks this rule, or that has no value, is skipped.
Props Reference
EmpowerBannerAd
| Prop | Type | Required | Description |
|---|---|---|---|
zoneId | string | { android?: string, ios?: string } | No* | Zone ID for the banner placement |
androidZoneId | string | No* | Android-specific zone ID |
iosZoneId | string | No* | iOS-specific zone ID |
customParameters | string | No | Custom targeting, such as "category=sports&tags=a,b". See Custom Targeting. |
autoHeight | boolean | No | iOS only. Resize the banner's height to the ad on display. The style height is used until the first ad arrives. Default false. See Multi-Size, Adaptive and Native Zones. |
onAdSizeChanged | (event) => void | No | iOS only. Called with { width, height, zoneId } in event.nativeEvent whenever the ad's size changes. |
keepAlive | boolean | No | iOS only. Keep the zone alive across unmount / scroll-off (pause instead of destroy) so a scroll-back reuses the loaded ad instantly. Use for persistent, always-present slots — in-feed MRECs in a recycling list (FlashList) and sticky banners. Default false. No-op on Android (the Android SDK handles recycling natively). See Banners in Lists. |
onAdStatusChanged | (event) => void | No | Called when ad status changes |
style | ViewStyle | No | Container style. Defaults to { width: '100%', minHeight: 50 } |
* At least one zone ID must be provided via zoneId, androidZoneId, or iosZoneId.
Imperative ref
EmpowerBannerAd forwards a ref exposing reload() to force a fresh ad request:
const bannerRef = useRef(null);
// ...
<EmpowerBannerAd ref={bannerRef} zoneId="YOUR_ZONE_ID" style={{ width: 300, height: 250 }} />
// later:
bannerRef.current?.reload();
Best Practices
- Set explicit dimensions — Always provide
widthandheightin thestyleprop matching the expected ad size (320x50, 320x100, or 300x250). For a zone whose fill size varies (multi-size, adaptive, native), addautoHeighton iOS. - Use
keepAlivefor persistent slots (iOS) — For in-feed MRECs in a recycling list and for a sticky banner, addkeepAliveso scroll-off / unmount pauses the zone instead of destroying and reloading it. See Banners in Lists. (A one-off banner on a simple screen doesn't need it.) - Handle failures gracefully — Use
onAdStatusChangedto hide the banner container or show fallback content when status isFAILED - One zone ID per component — Don't render the same zone ID in two live banner components at the same time. The SDK keeps one manager per zone, so two live views fight over it. (Reusing the same zone across different screens is fine — only one is on screen at a time.)
Ad Status Reference
| Status | Description |
|---|---|
INITIALIZING | Banner is loading |
READY | Banner is loaded and displayed |
FAILED | Banner failed to load |
WILL_LEAVE | User clicked the ad (leaving app) |