/**
* Provides the NativeAdView component, which manages loading and rendering of native ads using the
* AppLovin MAX SDK in React Native.
*/
import * as React from 'react';
import { forwardRef, useContext, useImperativeHandle, useRef, useState, useEffect, useCallback } from 'react';
import { findNodeHandle, View } from 'react-native';
import type { NativeSyntheticEvent, ViewProps } from 'react-native';
import AppLovinMAX from '../specs/NativeAppLovinMAXModule';
import NativeAdViewComponent, { Commands } from '../specs/AppLovinMAXNativeAdViewNativeComponent';
import type { AdInfoEvent, AdLoadFailedEvent } from '../specs/AppLovinMAXNativeAdViewNativeComponent';
import { NativeAdViewContext, NativeAdViewProvider } from './NativeAdViewProvider';
import type { NativeAdViewContextType } from './NativeAdViewProvider';
import type { NativeAdViewHandler, NativeAdViewProps } from '../types/NativeAdViewProps';
import { makeExtraParametersArray, makeLocalExtraParametersArray } from '../Utils';
/**
* The {@link NativeAdView} component renders a native ad and binds it to asset views:
*
* - {@link IconView}
* - {@link TitleView}
* - {@link AdvertiserView}
* - {@link StarRatingView}
* - {@link BodyView}
* - {@link MediaView}
* - {@link CallToActionView}
*
* Each asset view must be manually laid out and styled.
* The component automatically populates content once an ad is loaded.
* You can reload the ad using the component’s ref via `loadAd()`.
*
* **Note:** The AppLovin SDK must be initialized before using this component.
*
* ### Example:
* ```tsx
* { ... }}
* >
*
*
*
*
*
*
*
*
*
*
*
* ```
*
* For a complete implementation example, see:
* https://github.com/AppLovin/AppLovin-MAX-React-Native/blob/master/example/src/NativeAdViewExample.tsx
*/
export const NativeAdView = forwardRef(function NativeAdView(props, ref) {
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
(async () => {
const result = await AppLovinMAX.isInitialized();
setIsInitialized(result);
if (!result) {
console.warn('NativeAdView is mounted before the initialization of the AppLovin MAX React Native module.');
}
})();
}, []);
// Avoid rendering the NativeAdView if the SDK is not initialized
if (!isInitialized) {
return ;
}
return (
);
});
/**
* Extracts native ad info from a synthetic event and invokes the provided callback, if any.
* Ensures optional native ad fields have fallback defaults.
*/
const handleNativeAdViewEvent = (event: NativeSyntheticEvent, callback?: (adInfo: T) => void) => {
if (!callback) return;
callback(event.nativeEvent);
};
const NativeAdViewImpl = forwardRef(function NativeAdViewImpl(
{ adUnitId, placement, customData, extraParameters, localExtraParameters, onAdLoaded, onAdLoadFailed, onAdClicked, onAdRevenuePaid, children, style, ...otherProps },
ref
) {
// Context provides functions to manage native ad and native ad view state
const { titleRef, advertiserRef, bodyRef, callToActionRef, imageRef, optionViewRef, mediaViewRef, setNativeAd } = useContext(NativeAdViewContext) as NativeAdViewContextType;
const nativeAdViewRef = useRef | undefined>();
// Triggers a native ad load via the native command
const loadAd = useCallback(() => {
nativeAdViewRef.current && Commands.loadAd(nativeAdViewRef.current);
}, []);
const destroyAd = useCallback(() => {
nativeAdViewRef.current && Commands.destroyAd(nativeAdViewRef.current);
}, []);
useImperativeHandle(ref, () => ({ loadAd, destroyAd }), [loadAd, destroyAd]);
/**
* Updates the native asset view binding for a given view type (e.g., TitleView, MediaView).
*/
const updateAssetView = useCallback((assetViewRef: React.RefObject, type: string) => {
if (!nativeAdViewRef.current || !assetViewRef.current) return;
const node = findNodeHandle(assetViewRef.current);
if (node) {
Commands.updateAssetView(nativeAdViewRef.current, node, type);
}
}, []);
/**
* Handles native ad load event:
* - Updates context with the new native ad data
* - Notifies native module of updated asset view mappings
* - Triggers native rendering after all asset views are registered
*/
const onAdLoadedEvent = useCallback(
(event: NativeSyntheticEvent) => {
const nativeAdImpl = event.nativeEvent.nativeAdImpl;
if (nativeAdImpl) {
setNativeAd({ ...nativeAdImpl });
if (nativeAdImpl.title) updateAssetView(titleRef, 'TitleView');
if (nativeAdImpl.advertiser) updateAssetView(advertiserRef, 'AdvertiserView');
if (nativeAdImpl.body) updateAssetView(bodyRef, 'BodyView');
if (nativeAdImpl.callToAction) updateAssetView(callToActionRef, 'CallToActionView');
if (nativeAdImpl.url || nativeAdImpl.image || nativeAdImpl.imageSource) updateAssetView(imageRef, 'IconView');
if (nativeAdImpl.isOptionsViewAvailable) updateAssetView(optionViewRef, 'OptionsView');
if (nativeAdImpl.isMediaViewAvailable) updateAssetView(mediaViewRef, 'MediaView');
}
if (nativeAdViewRef.current) {
Commands.renderNativeAd(nativeAdViewRef.current);
}
handleNativeAdViewEvent(event, onAdLoaded);
},
[setNativeAd, onAdLoaded, updateAssetView, titleRef, advertiserRef, bodyRef, callToActionRef, imageRef, optionViewRef, mediaViewRef]
);
const onAdLoadFailedEvent = useCallback((event: NativeSyntheticEvent) => handleNativeAdViewEvent(event, onAdLoadFailed), [onAdLoadFailed]);
const onAdClickedEvent = useCallback((event: NativeSyntheticEvent) => handleNativeAdViewEvent(event, onAdClicked), [onAdClicked]);
const onAdRevenuePaidEvent = useCallback((event: NativeSyntheticEvent) => handleNativeAdViewEvent(event, onAdRevenuePaid), [onAdRevenuePaid]);
return (
(nativeAdViewRef.current = element ?? undefined)}
adUnitId={adUnitId}
placement={placement}
customData={customData}
extraParameters={makeExtraParametersArray(extraParameters)}
strLocalExtraParameters={makeLocalExtraParametersArray(localExtraParameters, 'str')}
boolLocalExtraParameters={makeLocalExtraParametersArray(localExtraParameters, 'bool')}
onAdLoadedEvent={onAdLoadedEvent}
onAdLoadFailedEvent={onAdLoadFailedEvent}
onAdClickedEvent={onAdClickedEvent}
onAdRevenuePaidEvent={onAdRevenuePaidEvent}
style={style}
{...otherProps}
>
{children}
);
});