Skip to content

Commit

Permalink
refactor: release resources properly
Browse files Browse the repository at this point in the history
  • Loading branch information
wjaykim committed Dec 27, 2024
1 parent 732cc2f commit 7813c21
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 47 deletions.
48 changes: 22 additions & 26 deletions RNGoogleMobileAdsExample/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -474,32 +474,28 @@ const NativeComponent = () => {
if (!nativeAd) {
return;
}
const listeners = [
nativeAd.addAdEventListener(NativeAdEventType.IMPRESSION, () => {
console.debug('Native ad impression');
}),
nativeAd.addAdEventListener(NativeAdEventType.CLICKED, () => {
console.debug('Native ad clicked');
}),
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_PLAYED, () => {
console.debug('Native ad video played');
}),
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_PAUSED, () => {
console.debug('Native ad video paused');
}),
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_ENDED, () => {
console.debug('Native ad video ended');
}),
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_MUTED, () => {
console.debug('Native ad video muted');
}),
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_UNMUTED, () => {
console.debug('Native ad video unmuted');
}),
];
return () => {
listeners.forEach(listener => listener.remove());
};
nativeAd.addAdEventListener(NativeAdEventType.IMPRESSION, () => {
console.debug('Native ad impression');
});
nativeAd.addAdEventListener(NativeAdEventType.CLICKED, () => {
console.debug('Native ad clicked');
});
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_PLAYED, () => {
console.debug('Native ad video played');
});
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_PAUSED, () => {
console.debug('Native ad video paused');
});
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_ENDED, () => {
console.debug('Native ad video ended');
});
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_MUTED, () => {
console.debug('Native ad video muted');
});
nativeAd.addAdEventListener(NativeAdEventType.VIDEO_UNMUTED, () => {
console.debug('Native ad video unmuted');
});
return () => nativeAd.destroy();
}, [nativeAd]);

if (!nativeAd) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ class ReactNativeGoogleMobileAdsNativeAdView(
post(measureAndLayout)
}

fun destroy() {
reloadJob?.cancel()
reloadJob = null
nativeAdView.removeView(viewGroup)
nativeAdView.destroy()
}

private val measureAndLayout = Runnable {
measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ class ReactNativeGoogleMobileAdsNativeAdViewManager(

override fun createViewInstance(context: ThemedReactContext): ReactNativeGoogleMobileAdsNativeAdView = ReactNativeGoogleMobileAdsNativeAdView(context)

override fun onDropViewInstance(adView: ReactNativeGoogleMobileAdsNativeAdView) {
super.onDropViewInstance(adView)
adView.destroy()
}

override fun prepareToRecycleView(
reactContext: ThemedReactContext,
view: ReactNativeGoogleMobileAdsNativeAdView
): ReactNativeGoogleMobileAdsNativeAdView? {
return null
}

@ReactProp(name = "responseId")
override fun setResponseId(adView: ReactNativeGoogleMobileAdsNativeAdView, responseId: String?) {
adView.setResponseId(responseId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ import com.google.android.gms.ads.nativead.NativeAdOptions
class ReactNativeGoogleMobileAdsNativeModule(
reactContext: ReactApplicationContext
) : NativeGoogleMobileAdsNativeModuleSpec(reactContext) {
private val loadedAds = HashMap<String, NativeAd>()
private val adHolders = HashMap<String, NativeAdHolder>()

override fun getName() = NAME

override fun load(adUnitId: String, requestOptions: ReadableMap, promise: Promise) {
val holder = NativeAdHolder(adUnitId, requestOptions)
holder.loadAd { nativeAd ->
val responseId = nativeAd.responseInfo?.responseId ?: return@loadAd
loadedAds[responseId] = nativeAd
adHolders[responseId] = holder

val data = Arguments.createMap()
data.putString("responseId", responseId)
Expand Down Expand Up @@ -75,12 +75,18 @@ class ReactNativeGoogleMobileAdsNativeModule(
}
}

override fun destroy(responseId: String) {
adHolders[responseId]?.destroy()
adHolders.remove(responseId)
}

fun getNativeAd(responseId: String): NativeAd? {
return loadedAds[responseId]
return adHolders[responseId]?.nativeAd
}

private inner class NativeAdHolder(private val adUnitId: String, private val requestOptions: ReadableMap) {
private var nativeAd: NativeAd? = null
var nativeAd: NativeAd? = null
private set

private val adListener: AdListener = object : AdListener() {
override fun onAdImpression() {
Expand Down Expand Up @@ -172,6 +178,11 @@ class ReactNativeGoogleMobileAdsNativeModule(
adLoader.loadAd(adRequest)
}

fun destroy() {
nativeAd?.destroy()
nativeAd = null
}

private fun emitAdEvent(type: String) {
val nativeAd = this.nativeAd ?: return
val payload = Arguments.createMap()
Expand Down
37 changes: 26 additions & 11 deletions ios/RNGoogleMobileAds/RNGoogleMobileAdsNativeModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@ typedef void (^RNGMANativeAdLoadCompletionHandler)(GADNativeAd *_Nullable native
@interface RNGMANativeAdHolder
: NSObject <GADNativeAdLoaderDelegate, GADNativeAdDelegate, GADVideoControllerDelegate>

@property GADNativeAd *nativeAd;
@property(strong, nullable) GADNativeAd *nativeAd;

- (instancetype)initWithNativeModule:(RNGoogleMobileAdsNativeModule *)nativeModule
adUnitId:(NSString *)adUnitId
requestOptions:(NSDictionary *)requestOptions;

- (void)loadWithCompletionHandler:(RNGMANativeAdLoadCompletionHandler)completionHandler;

- (void)dispose;

@end

@implementation RNGoogleMobileAdsNativeModule {
NSMutableSet<id> *_requestedAds;
NSMutableDictionary<NSString *, RNGMANativeAdHolder *> *_loadedAds;
NSMutableDictionary<NSString *, RNGMANativeAdHolder *> *_adHolders;
}

RCT_EXPORT_MODULE();
Expand All @@ -56,8 +57,7 @@ - (dispatch_queue_t)methodQueue {

- (instancetype)init {
if (self = [super init]) {
_requestedAds = [NSMutableSet set];
_loadedAds = [NSMutableDictionary dictionary];
_adHolders = [NSMutableDictionary dictionary];
}
return self;
}
Expand All @@ -69,18 +69,15 @@ - (void)load:(NSString *)adUnitId
RNGMANativeAdHolder *adHolder = [[RNGMANativeAdHolder alloc] initWithNativeModule:self
adUnitId:adUnitId
requestOptions:requestOptions];
[_requestedAds addObject:adHolder];

[adHolder loadWithCompletionHandler:^(GADNativeAd *nativeAd, NSError *error) {
[_requestedAds removeObject:adHolder];

if (error != nil) {
reject(@"ERROR_LOAD", error.description, error);
return;
}

NSString *responseId = nativeAd.responseInfo.responseIdentifier;
[_loadedAds setValue:adHolder forKey:responseId];
[_adHolders setValue:adHolder forKey:responseId];

resolve(@{
@"responseId" : responseId,
Expand All @@ -103,16 +100,21 @@ - (void)load:(NSString *)adUnitId
}];
}

- (void)destroy:(NSString *)responseId {
[[_adHolders valueForKey:responseId] dispose];
[_adHolders removeObjectForKey:responseId];
}

- (GADNativeAd *)nativeAdForResponseId:(NSString *)responseId {
return [_loadedAds valueForKey:responseId].nativeAd;
return [_adHolders valueForKey:responseId].nativeAd;
}

@end

#pragma mark - RNGMANativeAdHolder

@implementation RNGMANativeAdHolder {
RNGoogleMobileAdsNativeModule *_nativeModule;
__weak RNGoogleMobileAdsNativeModule *_nativeModule;
GADAdLoader *_adLoader;
GAMRequest *_adRequest;
RNGMANativeAdLoadCompletionHandler _completionHandler;
Expand Down Expand Up @@ -181,6 +183,14 @@ - (void)loadWithCompletionHandler:(RNGMANativeAdLoadCompletionHandler)completion
[_adLoader loadRequest:_adRequest];
}

- (void)dispose {
_nativeAd = nil;
_nativeModule = nil;
_adLoader = nil;
_adRequest = nil;
_completionHandler = nil;
}

#pragma mark - GADNativeAdLoaderDelegate

- (void)adLoader:(nonnull GADAdLoader *)adLoader
Expand All @@ -191,11 +201,13 @@ - (void)adLoader:(nonnull GADAdLoader *)adLoader
nativeAd.mediaContent.videoController.delegate = self;
}
_completionHandler(nativeAd, nil);
_completionHandler = nil;
}

- (void)adLoader:(nonnull GADAdLoader *)adLoader
didFailToReceiveAdWithError:(nonnull NSError *)error {
_completionHandler(nil, error);
_completionHandler = nil;
}

#pragma mark - GADNativeAdDelegate
Expand Down Expand Up @@ -245,6 +257,9 @@ - (void)videoControllerDidUnmuteVideo:(nonnull GADVideoController *)videoControl
}

- (void)emitAdEvent:(NSString *)type {
if (_nativeModule == nil || _nativeAd == nil) {
return;
}
[_nativeModule
emitOnAdEvent:@{@"responseId" : _nativeAd.responseInfo.responseIdentifier, @"type" : type}];
}
Expand Down
10 changes: 9 additions & 1 deletion ios/RNGoogleMobileAds/RNGoogleMobileAdsNativeView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ - (void)registerAsset:(NSString *)assetType reactTag:(NSInteger)reactTag {
#ifdef RCT_NEW_ARCH_ENABLED
GADMediaView *mediaView = ((RNGoogleMobileAdsMediaView *)view).contentView;
#else
GADMediaView *mediaView = (RNGoogleMobileAdsMediaView *) view);
GADMediaView *mediaView = (RNGoogleMobileAdsMediaView *) view;
#endif
[_nativeAdView setMediaView:mediaView];
[self reloadAd];
Expand Down Expand Up @@ -167,6 +167,14 @@ - (void)reloadAd {
dispatch_after(time, dispatch_get_main_queue(), _debouncedReload);
}

- (void)dealloc {
_nativeAdView = nil;
if (_debouncedReload != nil) {
dispatch_block_cancel(_debouncedReload);
_debouncedReload = nil;
}
}

@end

@implementation RNGoogleMobileAdsNativeViewManager
Expand Down
1 change: 1 addition & 0 deletions src/ads/native-ad/NativeAd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export class NativeAd {
}

destroy() {
NativeGoogleMobileAdsNativeModule.destroy(this.responseId);
this.nativeEventSubscription.remove();
this.removeAllAdEventListeners();
}
Expand Down
12 changes: 7 additions & 5 deletions src/ads/native-ad/NativeAsset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,25 @@ export enum NativeAssetType {
IMAGE = 'image',
}

export type NativeAssetProps = { assetType: NativeAssetType; children: ReactElement };
export type NativeAssetProps = {
assetType: NativeAssetType;
children: ReactElement;
};

export const NativeAsset = (props: NativeAssetProps) => {
const { assetType, children } = props;
const { viewRef } = useContext(NativeAdContext);
const ref = useRef(null);
const ref = useRef<React.Component>(null);

useEffect(() => {
const node = ref.current;
if (!node || !viewRef.current) {
if (!viewRef.current) {
return;
}
const node = ref.current;
const reactTag = findNodeHandle(node);
if (reactTag) {
Commands.registerAsset(viewRef.current, assetType, reactTag);
}
// TODO: unregister asset in cleanup?
}, [viewRef]);

if (!React.isValidElement(children)) {
Expand Down
1 change: 1 addition & 0 deletions src/specs/modules/NativeGoogleMobileAdsNativeModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export type NativeAdEventPayload = {

export interface Spec extends TurboModule {
load(adUnitId: string, requestOptions: UnsafeObject): Promise<NativeAdProps>;
destroy(responseId: string): void;
readonly onAdEvent: EventEmitter<NativeAdEventPayload>;
}

Expand Down

0 comments on commit 7813c21

Please sign in to comment.