@react-native-firebase/admob 배너광고 버그 수정하기(BugFix)

HoWWWWWhy·2022년 1월 30일
0

react-native

목록 보기
1/3

0. Reference 링크

1) npm 모듈
https://www.npmjs.com/package/@react-native-firebase/admob

2) 관련 issues
https://github.com/invertase/react-native-firebase/issues/5179

3) 공식 문서: Android Native UI Components
https://reactnative.dev/docs/0.65/native-components-android

4) patch 파일 만들기
https://github.com/ds300/patch-package

1. 배경

앱을 처음 출시한 지 약 1년 반 만에 드디어 애드몹 광고를 달았다.
광고를 늦게 단 이유는

  • 적어도 내가 생각하는 기본 기능을 구현한 뒤 광고를 달아야 한다고 생각했다.
  • 광고를 다는 것도 공부해야 했기에 더 중요한 다른 기능을 추가하다 보니 점점 늦어졌다.

막상 광고를 달려는데, 애정이 듬뿍 담긴 내 첫 앱이라 그런지 광고로 앱을 더럽히고 싶지는 않았다. 그래서 유저들이 사용할 때 가장 영향을 덜 받을만한 페이지에 배너 광고를 두 개 넣는 것으로 시작했다.

(자기계발 겸 부수입을 얻는 것이 목표이지만...)

2. 문제 현상

그렇게 나름 힘들게 광고를 달았는데, 앱 실행 후 첫 광고는 항상 뜨지 않았다. 사실 테스트 할 때부터 알고 있던 문제 현상이었는데, 먼저 업데이트하는 것이 우선이었기 때문에 일단 구글 플레이 스토어에 올려버렸다.

[ 요약 ]

  • 배너 광고가 달린 페이지에서 앱 실행 후 처음 요청하는 광고는 항상 불러오지 못한다. 좀 더 자세히 보니, 광고를 아예 요청하지 못한다.
  • 두 번째 요청부터는 문제없이 동작한다.

3. 문제 분석

Reference 링크의 관련 issues가 큰 힌트가 되었다.
npm 모듈이라고 항상 완벽한 법은 없다. 종종 고쳐야 하는 경우가 생긴다.
분석을 위해, 공식 문서를 참고하여 코드를 익혔다.

1) 파일 경로
root폴더\node_modules\@react-native-firebase\admob\android\src\main\java\io\invertase\firebase\admob\ReactNativeFirebaseAdMobBannerAdViewManager.java

2) 디버깅을 위해 +코드를 추가
size, unitId, request 중 하나 이상 null이기 때문에 광고 요청을 하지 못하고 return 되는 것으로 추정되었다.

package io.invertase.firebase.admob;

...

import java.util.Map;
+ import android.util.Log;

...

  @ReactProp(name = "unitId")
  public void setUnitId(ReactViewGroup reactViewGroup, String value) {
    + Log.d("TEST_ADMOB", "setUnitId");
    unitId = value;
  }

  @ReactProp(name = "request")
  public void setRequest(ReactViewGroup reactViewGroup, ReadableMap value) {
    + Log.d("TEST_ADMOB", "setRequest");
    request = ReactNativeFirebaseAdMobCommon.buildAdRequest(value);
    //requestAd(reactViewGroup);
  }

  @ReactProp(name = "size")
  public void setSize(ReactViewGroup reactViewGroup, String value) {
    + Log.d("TEST_ADMOB", "setSize");
    size = ReactNativeFirebaseAdMobCommon.getAdSize(value, reactViewGroup);

    int width;
    int height;
    WritableMap payload = Arguments.createMap();

    if (size == AdSize.SMART_BANNER) {
      width = (int) PixelUtil.toDIPFromPixel(size.getWidthInPixels(reactViewGroup.getContext()));
      height = (int) PixelUtil.toDIPFromPixel(size.getHeightInPixels(reactViewGroup.getContext()));
    } else {
      width = size.getWidth();
      height = size.getHeight();
    }

    payload.putDouble("width", width);
    payload.putDouble("height", height);

    if (size != AdSize.FLUID) {
      sendEvent(reactViewGroup, "onSizeChange", payload);
    }
    requestAd(reactViewGroup);
  }

  ...

  private void requestAd(ReactViewGroup reactViewGroup) {
    + Log.d("TEST_ADMOB", ""+size);
    + Log.d("TEST_ADMOB", ""+unitId);
    + Log.d("TEST_ADMOB", ""+request);
    if (size == null || unitId == null || request == null) {
      return;
    }

    if (requested) {
      resetAdView(reactViewGroup);
    }

    AdView adView = getAdView(reactViewGroup);
    adView.setAdUnitId(unitId);
    adView.setAdSize(size);
    adView.loadAd(request);

    requested = true;
  }
...

3) 디버깅 결과 (Android Studio의 Logcat TEST_ADMOB filtering)

2022-01-30 22:09:40.829 28333-28333/com.howwwwwhy.heartwarming D/TEST_ADMOB: setSize
2022-01-30 22:09:40.830 28333-28333/com.howwwwwhy.heartwarming D/TEST_ADMOB: 320x100_as
2022-01-30 22:09:40.830 28333-28333/com.howwwwwhy.heartwarming D/TEST_ADMOB: null
2022-01-30 22:09:40.830 28333-28333/com.howwwwwhy.heartwarming D/TEST_ADMOB: null
2022-01-30 22:09:40.830 28333-28333/com.howwwwwhy.heartwarming D/TEST_ADMOB: setUnitId
2022-01-30 22:09:40.830 28333-28333/com.howwwwwhy.heartwarming D/TEST_ADMOB: setRequest

setSize에서 requestAd 함수를 호출한 후에 setUnitId와 setRequest가 호출되는 것이 원인인 듯 했다. 예상대로 unitId와 request가 null이기 때문에 native 코드인 loadAd를 실행하지 못하고 requestAd 함수가 종료되었다. 이렇게 되는 이유가 뭘지 추정했다.

4. 문제 해결

1) Banner 광고를 반환하는 곳 발견

  • App의 컴포넌트 호출 부분
<BannerAd
  unitId={adUnitId}
  size={BannerAdSize.LARGE_BANNER} //Large banner: 320 x 100 density-independent pixels
  requestOptions={{
    requestNonPersonalizedAdsOnly: false,
  }}
  onAdFailedToLoad={error => {
    console.log('Advert failed to load: ', error);
    setShowBanner(false);
    setAltanativeText('광고 자리입니다.');
  }}
/>
  • BannerAd.js의 BannerView 호출 부분
    파일 경로: root폴더\node_modules\@react-native-firebase\admob\lib\ads\BannerAd.js
  return (
    <ReactNativeFirebaseAdMobBannerView
      size={size}
      style={style}
      unitId={unitId}
      request={validateAdRequestOptions(requestOptions)}
      onNativeEvent={onNativeEvent}
    />
  );

여기서 느낌이 왔다. props 순서가 size, unitId, request 순서였다.

<공식 문서>
Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with @ReactProp (or @ReactPropGroup).

JavaScript의 속성은 @ReactProp으로 annotate된 setter method로 반영되어야 한다. request가 셋 중 맨 마지막 props였으므로 java파일의 setSize에서 호출하는 requestAd 함수를 setRequest로 옮겨보았다.

  @ReactProp(name = "request")
  public void setRequest(ReactViewGroup reactViewGroup, ReadableMap value) {
    Log.d("TEST_ADMOB", "setRequest");
    request = ReactNativeFirebaseAdMobCommon.buildAdRequest(value);
    + requestAd(reactViewGroup);
  }

2) 디버깅 결과

2022-01-30 23:08:27.329 31512-31512/com.howwwwwhy.heartwarming D/TEST_ADMOB: setSize
2022-01-30 23:08:27.329 31512-31512/com.howwwwwhy.heartwarming D/TEST_ADMOB: setUnitId
2022-01-30 23:08:27.329 31512-31512/com.howwwwwhy.heartwarming D/TEST_ADMOB: setRequest
2022-01-30 23:08:27.330 31512-31512/com.howwwwwhy.heartwarming D/TEST_ADMOB: 320x100_as
2022-01-30 23:08:27.330 31512-31512/com.howwwwwhy.heartwarming D/TEST_ADMOB: ca-app-pub-*****/*****
2022-01-30 23:08:27.330 31512-31512/com.howwwwwhy.heartwarming D/TEST_ADMOB: com.google.android.gms.ads.AdRequest@d26fc53

size, unitId, request 모두 값이 들어가 있었고 정상적으로 광고를 요청했다.

3) patch 파일 생성

  • patch-package 사용
npx patch-package @react-native-firebase/admob

root의 patches 폴더에
@react-native-firebase+admob+11.5.0.patch 파일이 생성된다.

5. 마무리

setter method에서 함수를 호출하는 것은 좋지 않은 방법인 것 같다. props의 순서에 상관없이 requestAd 함수는 동일하게 호출되어야 한다.
추가로 시간이 날 때, Android Native UI Components와 admob 모듈에 대한 코드를 좀 더 이해하여 props 순서와 관계없이 같은 동작을 할 수 있도록 할 예정이다.

보통 npm 모듈을 수정하게 되는 경우, 다음 버전에서는 문제가 개선되리라 믿게 된다. 하지만 슬프게도 issue의 comment를 읽던 중 아래 내용을 발견했다.

https://github.com/invertase/react-native-firebase/issues/5179#issuecomment-844355532

mikehardy commented on 20 May 2021
With apologies Admob no longer present in v12 as it was removed in upstream SDKs so there are no APIs to wrap any longer - please stay with v11.5 until migration to another project (either from this same code in a new repo - planned, or a different project)

현재(2022년 1월 30일) @react-native-firebase/admob의 최신 버전이 11.5.0이고 함께 설치되어야 하는 @react-native-firebase/app의 최신 버전은 14.3.0이다. 하지만 admob을 쓰기 위해서는 app도 11.5.0으로 동일하게 맞춰주어야 한다. comment에 의하면 v12부터 admob이 제외되었고 실제로 github에서 react-native-firebase/packages 폴더를 확인해보니 정확히 v12.0.0부터 admob 폴더가 빠졌다. 이걸 왜 지금 알게 된 걸까...

만약 firebase/app의 다른 패키지를 사용해야 할 날이 온다면,
admob 모듈을 다른 모듈로 바꿔야 할 것이다. 마음의 준비를 할 수 있게 된 것으로 만족해야겠다.

0개의 댓글