@capacitor-community/admob 8.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CapacitorCommunityAdmob.podspec +1 -1
  2. package/Package.swift +1 -1
  3. package/README.md +635 -169
  4. package/android/build.gradle +1 -1
  5. package/android/src/main/java/com/getcapacitor/community/admob/AdMob.java +73 -23
  6. package/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdManager.kt +92 -0
  7. package/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdPlugin.kt +122 -0
  8. package/android/src/main/java/com/getcapacitor/community/admob/appopen/AppOpenAdPluginEvents.kt +10 -0
  9. package/android/src/main/java/com/getcapacitor/community/admob/banner/BannerAdPluginEvents.kt +2 -1
  10. package/android/src/main/java/com/getcapacitor/community/admob/banner/BannerExecutor.java +79 -29
  11. package/android/src/main/java/com/getcapacitor/community/admob/helpers/FullscreenPluginCallback.kt +7 -2
  12. package/android/src/main/java/com/getcapacitor/community/admob/interstitial/AdInterstitialExecutor.java +21 -3
  13. package/android/src/main/java/com/getcapacitor/community/admob/interstitial/InterstitialAdCallbackAndListeners.kt +12 -2
  14. package/android/src/main/java/com/getcapacitor/community/admob/interstitial/InterstitialAdPluginPluginEvent.kt +1 -0
  15. package/android/src/main/java/com/getcapacitor/community/admob/models/AdMobRevenueData.kt +15 -0
  16. package/android/src/main/java/com/getcapacitor/community/admob/models/AdOptions.java +10 -0
  17. package/android/src/main/java/com/getcapacitor/community/admob/rewarded/AdRewardExecutor.java +20 -3
  18. package/android/src/main/java/com/getcapacitor/community/admob/rewarded/RewardAdPluginEvents.kt +1 -0
  19. package/android/src/main/java/com/getcapacitor/community/admob/rewarded/RewardedAdCallbackAndListeners.kt +14 -4
  20. package/android/src/main/java/com/getcapacitor/community/admob/rewardedinterstitial/AdRewardInterstitialExecutor.java +20 -3
  21. package/android/src/main/java/com/getcapacitor/community/admob/rewardedinterstitial/RewardInterstitialAdPluginEvents.kt +1 -0
  22. package/android/src/main/java/com/getcapacitor/community/admob/rewardedinterstitial/RewardedInterstitialAdCallbackAndListeners.kt +14 -3
  23. package/dist/docs.json +740 -193
  24. package/dist/esm/app-open/app-open-ad-options.interface.d.ts +9 -0
  25. package/dist/esm/app-open/app-open-ad-options.interface.js +2 -0
  26. package/dist/esm/app-open/app-open-ad-options.interface.js.map +1 -0
  27. package/dist/esm/app-open/app-open-ad-plugin-events.enum.d.ts +26 -0
  28. package/dist/esm/app-open/app-open-ad-plugin-events.enum.js +28 -0
  29. package/dist/esm/app-open/app-open-ad-plugin-events.enum.js.map +1 -0
  30. package/dist/esm/app-open/app-open-definitions.interface.d.ts +50 -0
  31. package/dist/esm/app-open/app-open-definitions.interface.js +2 -0
  32. package/dist/esm/app-open/app-open-definitions.interface.js.map +1 -0
  33. package/dist/esm/app-open/index.d.ts +3 -0
  34. package/dist/esm/app-open/index.js +4 -0
  35. package/dist/esm/app-open/index.js.map +1 -0
  36. package/dist/esm/banner/banner-ad-options.interface.d.ts +5 -6
  37. package/dist/esm/banner/banner-ad-plugin-events.enum.d.ts +17 -4
  38. package/dist/esm/banner/banner-ad-plugin-events.enum.js +16 -3
  39. package/dist/esm/banner/banner-ad-plugin-events.enum.js.map +1 -1
  40. package/dist/esm/banner/banner-ad-position.enum.d.ts +3 -3
  41. package/dist/esm/banner/banner-ad-position.enum.js +3 -3
  42. package/dist/esm/banner/banner-ad-size.enum.d.ts +4 -3
  43. package/dist/esm/banner/banner-ad-size.enum.js +4 -3
  44. package/dist/esm/banner/banner-ad-size.enum.js.map +1 -1
  45. package/dist/esm/banner/banner-definitions.interface.d.ts +17 -10
  46. package/dist/esm/banner/banner-size.interface.d.ts +8 -1
  47. package/dist/esm/definitions.d.ts +19 -17
  48. package/dist/esm/definitions.js.map +1 -1
  49. package/dist/esm/index.d.ts +1 -0
  50. package/dist/esm/index.js +1 -0
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/interstitial/interstitial-ad-plugin-events.enum.d.ts +10 -6
  53. package/dist/esm/interstitial/interstitial-ad-plugin-events.enum.js +9 -5
  54. package/dist/esm/interstitial/interstitial-ad-plugin-events.enum.js.map +1 -1
  55. package/dist/esm/interstitial/interstitial-definitions.interface.d.ts +24 -4
  56. package/dist/esm/reward/reward-ad-options.interface.d.ts +7 -6
  57. package/dist/esm/reward/reward-ad-plugin-events.enum.d.ts +13 -12
  58. package/dist/esm/reward/reward-ad-plugin-events.enum.js +12 -11
  59. package/dist/esm/reward/reward-ad-plugin-events.enum.js.map +1 -1
  60. package/dist/esm/reward/reward-definitions.interface.d.ts +27 -4
  61. package/dist/esm/reward/reward-item.interface.d.ts +3 -4
  62. package/dist/esm/reward-interstitial/reward-interstitial-ad-options.interface.d.ts +7 -6
  63. package/dist/esm/reward-interstitial/reward-interstitial-ad-plugin-events.enum.d.ts +13 -12
  64. package/dist/esm/reward-interstitial/reward-interstitial-ad-plugin-events.enum.js +12 -11
  65. package/dist/esm/reward-interstitial/reward-interstitial-ad-plugin-events.enum.js.map +1 -1
  66. package/dist/esm/reward-interstitial/reward-interstitial-definitions.interface.d.ts +30 -7
  67. package/dist/esm/reward-interstitial/reward-interstitial-item.interface.d.ts +3 -4
  68. package/dist/esm/shared/ad-load-info.interface.d.ts +6 -0
  69. package/dist/esm/shared/ad-mob-revenue-data.interface.d.ts +50 -0
  70. package/dist/esm/shared/ad-mob-revenue-data.interface.js +23 -0
  71. package/dist/esm/shared/ad-mob-revenue-data.interface.js.map +1 -0
  72. package/dist/esm/shared/ad-options.interface.d.ts +9 -11
  73. package/dist/esm/shared/ad-show-options.interface.d.ts +12 -0
  74. package/dist/esm/shared/ad-show-options.interface.js +2 -0
  75. package/dist/esm/shared/ad-show-options.interface.js.map +1 -0
  76. package/dist/esm/shared/admob-error.interface.d.ts +3 -2
  77. package/dist/esm/shared/index.d.ts +2 -0
  78. package/dist/esm/shared/index.js +2 -0
  79. package/dist/esm/shared/index.js.map +1 -1
  80. package/dist/esm/shared/tracking-authorization-status.interface.d.ts +6 -1
  81. package/dist/esm/web.d.ts +13 -4
  82. package/dist/esm/web.js +25 -9
  83. package/dist/esm/web.js.map +1 -1
  84. package/dist/plugin.cjs.js +131 -45
  85. package/dist/plugin.cjs.js.map +1 -1
  86. package/dist/plugin.js +131 -45
  87. package/dist/plugin.js.map +1 -1
  88. package/ios/Sources/AdMobPlugin/AdMobPlugin.swift +30 -4
  89. package/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdManager.swift +112 -0
  90. package/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPlugin.swift +101 -0
  91. package/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPluginEvents.swift +8 -0
  92. package/ios/Sources/AdMobPlugin/Banner/BannerAdPluginEvents.swift +1 -0
  93. package/ios/Sources/AdMobPlugin/Banner/BannerExecutor.swift +20 -6
  94. package/ios/Sources/AdMobPlugin/Interstitial/AdInterstitialExecutor.swift +41 -5
  95. package/ios/Sources/AdMobPlugin/Interstitial/InterstitialAdPluginEvents.swift +1 -0
  96. package/ios/Sources/AdMobPlugin/Rewarded/AdRewardExecutor.swift +44 -5
  97. package/ios/Sources/AdMobPlugin/Rewarded/RewardAdPluginEvents.swift +1 -0
  98. package/ios/Sources/AdMobPlugin/RewardedInterstitial/AdRewardInterstitialExecutor.swift +45 -6
  99. package/ios/Sources/AdMobPlugin/RewardedInterstitial/RewardInterstitialAdPluginEvents.swift +1 -0
  100. package/package.json +2 -1
@@ -4,7 +4,7 @@ ext {
4
4
  androidxCoordinatorLayoutVersion = project.hasProperty('androidxCoordinatorLayoutVersion') ? rootProject.ext.androidxCoordinatorLayoutVersion : '1.3.0'
5
5
  androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
6
6
  androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
7
- playServicesAdsVersion = project.hasProperty('playServicesAdsVersion') ? rootProject.ext.playServicesAdsVersion : '24.9.+'
7
+ playServicesAdsVersion = project.hasProperty('playServicesAdsVersion') ? rootProject.ext.playServicesAdsVersion : '25.4.+'
8
8
  userMessagingPlatformVersion = project.hasProperty('userMessagingPlatformVersion') ? rootProject.ext.userMessagingPlatformVersion : '4.0.0'
9
9
  androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.15.0'
10
10
  }
@@ -1,6 +1,9 @@
1
1
  package com.getcapacitor.community.admob;
2
2
 
3
3
  import android.Manifest;
4
+ import android.app.Activity;
5
+ import android.os.Handler;
6
+ import android.os.Looper;
4
7
  import com.getcapacitor.JSArray;
5
8
  import com.getcapacitor.JSObject;
6
9
  import com.getcapacitor.Plugin;
@@ -8,6 +11,7 @@ import com.getcapacitor.PluginCall;
8
11
  import com.getcapacitor.PluginMethod;
9
12
  import com.getcapacitor.annotation.CapacitorPlugin;
10
13
  import com.getcapacitor.annotation.Permission;
14
+ import com.getcapacitor.community.admob.appopen.AppOpenAdPlugin;
11
15
  import com.getcapacitor.community.admob.banner.BannerExecutor;
12
16
  import com.getcapacitor.community.admob.consent.AdConsentExecutor;
13
17
  import com.getcapacitor.community.admob.helpers.AuthorizationStatusEnum;
@@ -34,18 +38,21 @@ public class AdMob extends Plugin {
34
38
  this::notifyListeners,
35
39
  getLogTag()
36
40
  );
41
+
37
42
  private final AdRewardExecutor adRewardExecutor = new AdRewardExecutor(
38
43
  this::getContext,
39
44
  this::getActivity,
40
45
  this::notifyListeners,
41
46
  getLogTag()
42
47
  );
48
+
43
49
  private final AdRewardInterstitialExecutor adRewardInterstitialExecutor = new AdRewardInterstitialExecutor(
44
50
  this::getContext,
45
51
  this::getActivity,
46
52
  this::notifyListeners,
47
53
  getLogTag()
48
54
  );
55
+
49
56
  private final AdInterstitialExecutor adInterstitialExecutor = new AdInterstitialExecutor(
50
57
  this::getContext,
51
58
  this::getActivity,
@@ -61,23 +68,52 @@ public class AdMob extends Plugin {
61
68
  getLogTag()
62
69
  );
63
70
 
64
- // Initialize AdMob with appId
71
+ private final AppOpenAdPlugin appOpenAdPlugin = new AppOpenAdPlugin();
72
+
73
+ @PluginMethod
74
+ public void loadAppOpen(final PluginCall call) {
75
+ appOpenAdPlugin.loadAppOpen(getContext(), getActivity(), call, this::notifyListeners);
76
+ }
77
+
78
+ @PluginMethod
79
+ public void showAppOpen(final PluginCall call) {
80
+ appOpenAdPlugin.showAppOpen(getActivity(), call, this::notifyListeners);
81
+ }
82
+
83
+ @PluginMethod
84
+ public void isAppOpenLoaded(final PluginCall call) {
85
+ appOpenAdPlugin.isAppOpenLoaded(getActivity(), call);
86
+ }
87
+
88
+ // ---------------------------------------------------------
89
+ // MAIN METHODS
90
+ // ---------------------------------------------------------
91
+
65
92
  @PluginMethod
66
93
  public void initialize(final PluginCall call) {
67
94
  this.setRequestConfiguration(call);
68
95
 
69
- try {
70
- MobileAds.initialize(
71
- getContext(),
72
- new OnInitializationCompleteListener() {
73
- @Override
74
- public void onInitializationComplete(InitializationStatus initializationStatus) {}
75
- }
76
- );
77
- bannerExecutor.initialize();
78
- call.resolve();
79
- } catch (Exception ex) {
80
- call.reject(ex.getLocalizedMessage(), ex);
96
+ // Same as banner/interstitial: bridge thread is not the UI thread — MobileAds + view setup must run on main.
97
+ Runnable initOnMain = () -> {
98
+ try {
99
+ MobileAds.initialize(
100
+ getContext(),
101
+ new OnInitializationCompleteListener() {
102
+ @Override
103
+ public void onInitializationComplete(InitializationStatus initializationStatus) {}
104
+ }
105
+ );
106
+ bannerExecutor.initialize();
107
+ call.resolve();
108
+ } catch (Exception ex) {
109
+ call.reject(ex.getLocalizedMessage(), ex);
110
+ }
111
+ };
112
+ Activity activity = getActivity();
113
+ if (activity != null) {
114
+ activity.runOnUiThread(initOnMain);
115
+ } else {
116
+ new Handler(Looper.getMainLooper()).post(initOnMain);
81
117
  }
82
118
  }
83
119
 
@@ -93,7 +129,10 @@ public class AdMob extends Plugin {
93
129
  call.resolve(response);
94
130
  }
95
131
 
96
- // User Consent
132
+ // ---------------------------------------------------------
133
+ // USER CONSENT
134
+ // ---------------------------------------------------------
135
+
97
136
  @PluginMethod
98
137
  public void requestConsentInfo(final PluginCall call) {
99
138
  adConsentExecutor.requestConsentInfo(call, this::notifyListeners);
@@ -114,6 +153,10 @@ public class AdMob extends Plugin {
114
153
  adConsentExecutor.resetConsentInfo(call, this::notifyListeners);
115
154
  }
116
155
 
156
+ // ---------------------------------------------------------
157
+ // APP SETTINGS
158
+ // ---------------------------------------------------------
159
+
117
160
  @PluginMethod
118
161
  public void setApplicationMuted(final PluginCall call) {
119
162
  Boolean muted = call.getBoolean("muted");
@@ -136,41 +179,48 @@ public class AdMob extends Plugin {
136
179
  call.resolve();
137
180
  }
138
181
 
139
- // Show a banner Ad
182
+ // ---------------------------------------------------------
183
+ // BANNER ADS
184
+ // ---------------------------------------------------------
185
+
140
186
  @PluginMethod
141
187
  public void showBanner(final PluginCall call) {
142
188
  bannerExecutor.showBanner(call);
143
189
  }
144
190
 
145
- // Hide the banner, remove it from screen, but can show it later
146
191
  @PluginMethod
147
192
  public void hideBanner(final PluginCall call) {
148
193
  bannerExecutor.hideBanner(call);
149
194
  }
150
195
 
151
- // Resume the banner, show it after hide
152
196
  @PluginMethod
153
197
  public void resumeBanner(final PluginCall call) {
154
198
  bannerExecutor.resumeBanner(call);
155
199
  }
156
200
 
157
- // Destroy the banner, remove it from screen.
158
201
  @PluginMethod
159
202
  public void removeBanner(final PluginCall call) {
160
203
  bannerExecutor.removeBanner(call);
161
204
  }
162
205
 
206
+ // ---------------------------------------------------------
207
+ // INTERSTITIAL ADS
208
+ // ---------------------------------------------------------
209
+
163
210
  @PluginMethod
164
211
  public void prepareInterstitial(final PluginCall call) {
165
212
  adInterstitialExecutor.prepareInterstitial(call, this::notifyListeners);
166
213
  }
167
214
 
168
- // Show interstitial Ad
169
215
  @PluginMethod
170
216
  public void showInterstitial(final PluginCall call) {
171
217
  adInterstitialExecutor.showInterstitial(call, this::notifyListeners);
172
218
  }
173
219
 
220
+ // ---------------------------------------------------------
221
+ // REWARDED ADS
222
+ // ---------------------------------------------------------
223
+
174
224
  @PluginMethod
175
225
  public void prepareRewardVideoAd(final PluginCall call) {
176
226
  adRewardExecutor.prepareRewardVideoAd(call, this::notifyListeners);
@@ -191,10 +241,10 @@ public class AdMob extends Plugin {
191
241
  adRewardInterstitialExecutor.showRewardInterstitialAd(call, this::notifyListeners);
192
242
  }
193
243
 
194
- /**
195
- * @see <a href="https://developers.google.com/admob/android/test-ads#enable_test_devices">Test Devices</a>
196
- * @see <a href="https://developers.google.com/admob/android/targeting">Target Settings</a>
197
- */
244
+ // ---------------------------------------------------------
245
+ // REQUEST CONFIGURATION
246
+ // ---------------------------------------------------------
247
+
198
248
  private void setRequestConfiguration(final PluginCall call) {
199
249
  // Testing Devices
200
250
  final boolean initializeForTesting = call.getBoolean("initializeForTesting", false);
@@ -0,0 +1,92 @@
1
+ package com.getcapacitor.community.admob.appopen
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import com.google.android.gms.ads.AdError
6
+ import com.google.android.gms.ads.AdRequest
7
+ import com.google.android.gms.ads.FullScreenContentCallback
8
+ import com.google.android.gms.ads.AdValue
9
+ import com.google.android.gms.ads.LoadAdError
10
+ import com.google.android.gms.ads.appopen.AppOpenAd
11
+
12
+ class AppOpenAdManager(val adUnitId: String) {
13
+
14
+ private var appOpenAd: AppOpenAd? = null
15
+ private var isLoadingAd = false
16
+ private var isShowingAd = false
17
+
18
+ val isAdLoaded: Boolean
19
+ get() = appOpenAd != null
20
+
21
+ fun loadAd(context: Context, onLoaded: () -> Unit, onFailed: (LoadAdError?) -> Unit, onPaidEvent: (AdValue, String, String) -> Unit) {
22
+ if (appOpenAd != null) {
23
+ onLoaded()
24
+ return
25
+ }
26
+
27
+ if (isLoadingAd) {
28
+ onFailed(null)
29
+ return
30
+ }
31
+
32
+ isLoadingAd = true
33
+ val request = AdRequest.Builder().build()
34
+
35
+ // play-services-ads 24.x: orientation overload removed; SDK picks orientation from the activity.
36
+ AppOpenAd.load(
37
+ context,
38
+ adUnitId,
39
+ request,
40
+ object : AppOpenAd.AppOpenAdLoadCallback() {
41
+ override fun onAdLoaded(ad: AppOpenAd) {
42
+ appOpenAd = ad
43
+ isLoadingAd = false
44
+ ad.setOnPaidEventListener { adValue ->
45
+ val networkName = ad.responseInfo?.mediationAdapterClassName ?: ""
46
+ val impressionId = ad.responseInfo?.responseId ?: ""
47
+ onPaidEvent(adValue, networkName, impressionId)
48
+ }
49
+ onLoaded()
50
+ }
51
+
52
+ override fun onAdFailedToLoad(loadAdError: LoadAdError) {
53
+ isLoadingAd = false
54
+ onFailed(loadAdError)
55
+ }
56
+ }
57
+ )
58
+ }
59
+
60
+ fun showAdIfAvailable(
61
+ activity: Activity,
62
+ onOpened: () -> Unit,
63
+ onClosed: () -> Unit,
64
+ onFailedToShow: (AdError?) -> Unit
65
+ ) {
66
+ if (appOpenAd == null || isShowingAd) {
67
+ onFailedToShow(null)
68
+ return
69
+ }
70
+
71
+ isShowingAd = true
72
+ appOpenAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
73
+ override fun onAdShowedFullScreenContent() {
74
+ onOpened()
75
+ }
76
+
77
+ override fun onAdDismissedFullScreenContent() {
78
+ appOpenAd = null
79
+ isShowingAd = false
80
+ onClosed()
81
+ }
82
+
83
+ override fun onAdFailedToShowFullScreenContent(adError: AdError) {
84
+ appOpenAd = null
85
+ isShowingAd = false
86
+ onFailedToShow(adError)
87
+ }
88
+ }
89
+
90
+ appOpenAd?.show(activity)
91
+ }
92
+ }
@@ -0,0 +1,122 @@
1
+ package com.getcapacitor.community.admob.appopen
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import com.getcapacitor.JSObject
8
+ import com.getcapacitor.PluginCall
9
+ import com.getcapacitor.community.admob.models.AdMobPluginError
10
+ import com.getcapacitor.community.admob.models.AdMobRevenueData
11
+
12
+ class AppOpenAdPlugin {
13
+
14
+ fun interface EventNotifier {
15
+ fun notify(eventName: String, data: JSObject)
16
+ }
17
+
18
+ private val preparedManagers = LinkedHashMap<String, AppOpenAdManager>()
19
+ private var lastPreparedAdId: String? = null
20
+
21
+ private fun runOnMain(activity: Activity?, runnable: Runnable) {
22
+ if (activity != null) {
23
+ activity.runOnUiThread(runnable)
24
+ } else {
25
+ Handler(Looper.getMainLooper()).post(runnable)
26
+ }
27
+ }
28
+
29
+ fun loadAppOpen(context: Context?, activity: Activity?, call: PluginCall, notifier: EventNotifier) {
30
+ if (context == null) {
31
+ call.reject("Context is not available")
32
+ return
33
+ }
34
+
35
+ val adUnitId = call.getString("adId")
36
+ if (adUnitId == null) {
37
+ call.reject("adId is required")
38
+ return
39
+ }
40
+
41
+ val appContext = context.applicationContext
42
+ runOnMain(activity) {
43
+ val manager = preparedManagers.getOrPut(adUnitId) { AppOpenAdManager(adUnitId) }
44
+
45
+ manager.loadAd(
46
+ appContext,
47
+ onLoaded = {
48
+ lastPreparedAdId = adUnitId
49
+ val adInfo = JSObject().apply {
50
+ put("adUnitId", adUnitId)
51
+ }
52
+ notifier.notify(AppOpenAdPluginEvents.Loaded, adInfo)
53
+ call.resolve(adInfo)
54
+ },
55
+ onFailed = { loadAdError ->
56
+ val errorMessage = loadAdError?.message ?: "Failed to load App Open Ad"
57
+ val errorCode = loadAdError?.code ?: -1
58
+ notifier.notify(AppOpenAdPluginEvents.FailedToLoad, AdMobPluginError(errorCode, errorMessage))
59
+ call.reject(errorMessage)
60
+ },
61
+ onPaidEvent = { adValue, networkName, impressionId ->
62
+ val revenueData = AdMobRevenueData(adValue, adUnitId, networkName, impressionId)
63
+ notifier.notify(AppOpenAdPluginEvents.AdImpression, revenueData)
64
+ }
65
+ )
66
+ }
67
+ }
68
+
69
+ fun showAppOpen(activity: Activity?, call: PluginCall, notifier: EventNotifier) {
70
+ if (activity == null) {
71
+ call.reject("Activity is not available")
72
+ return
73
+ }
74
+
75
+ val adId = call.getString("adId") ?: lastPreparedAdId
76
+
77
+ runOnMain(activity) {
78
+ val manager = if (adId != null) preparedManagers[adId] else null
79
+
80
+ if (manager == null || !manager.isAdLoaded) {
81
+ call.reject("App Open Ad is not loaded")
82
+ return@runOnMain
83
+ }
84
+
85
+ manager.showAdIfAvailable(
86
+ activity,
87
+ onOpened = {
88
+ notifier.notify(AppOpenAdPluginEvents.Opened, JSObject())
89
+ },
90
+ onClosed = {
91
+ preparedManagers.remove(adId)
92
+ if (lastPreparedAdId == adId) {
93
+ lastPreparedAdId = preparedManagers.entries.lastOrNull { it.value.isAdLoaded }?.key
94
+ }
95
+ notifier.notify(AppOpenAdPluginEvents.Closed, JSObject())
96
+ call.resolve()
97
+ },
98
+ onFailedToShow = { adError ->
99
+ preparedManagers.remove(adId)
100
+ if (lastPreparedAdId == adId) {
101
+ lastPreparedAdId = preparedManagers.entries.lastOrNull { it.value.isAdLoaded }?.key
102
+ }
103
+ val errorMessage = adError?.message ?: "Failed to show App Open Ad"
104
+ val errorCode = adError?.code ?: -1
105
+ notifier.notify(AppOpenAdPluginEvents.FailedToShow, AdMobPluginError(errorCode, errorMessage))
106
+ call.reject(errorMessage)
107
+ }
108
+ )
109
+ }
110
+ }
111
+
112
+ fun isAppOpenLoaded(activity: Activity?, call: PluginCall) {
113
+ val adId = call.getString("adId") ?: lastPreparedAdId
114
+ runOnMain(activity) {
115
+ val loaded = adId?.let { preparedManagers[it]?.isAdLoaded } ?: false
116
+ val result = JSObject().apply {
117
+ put("value", loaded)
118
+ }
119
+ call.resolve(result)
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,10 @@
1
+ package com.getcapacitor.community.admob.appopen
2
+
3
+ object AppOpenAdPluginEvents {
4
+ const val Loaded = "appOpenAdLoaded"
5
+ const val FailedToLoad = "appOpenAdFailedToLoad"
6
+ const val Opened = "appOpenAdOpened"
7
+ const val Closed = "appOpenAdClosed"
8
+ const val FailedToShow = "appOpenAdFailedToShow"
9
+ const val AdImpression = "appOpenAdImpression"
10
+ }
@@ -8,4 +8,5 @@ enum class BannerAdPluginEvents(val webEventName: String) {
8
8
  Loaded("bannerAdLoaded"),
9
9
  Clicked("bannerAdClicked"),
10
10
  AdImpression("bannerAdImpression"),
11
- }
11
+ AdPaid("bannerAdPaid"),
12
+ }
@@ -18,6 +18,7 @@ import com.getcapacitor.PluginCall;
18
18
  import com.getcapacitor.community.admob.helpers.AdViewIdHelper;
19
19
  import com.getcapacitor.community.admob.helpers.RequestHelper;
20
20
  import com.getcapacitor.community.admob.models.AdMobPluginError;
21
+ import com.getcapacitor.community.admob.models.AdMobRevenueData;
21
22
  import com.getcapacitor.community.admob.models.AdOptions;
22
23
  import com.getcapacitor.community.admob.models.Executor;
23
24
  import com.google.android.gms.ads.AdListener;
@@ -103,27 +104,10 @@ public class BannerExecutor extends Executor {
103
104
  break;
104
105
  }
105
106
 
106
- // set Safe Area only for Android 15+
107
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
108
- View rootView = activitySupplier.get().getWindow().getDecorView();
109
- rootView.setOnApplyWindowInsetsListener((v, insets) -> {
110
- int bottomInset = insets.getSystemWindowInsetBottom();
111
- int topInset = insets.getSystemWindowInsetTop();
112
-
113
- if ("TOP_CENTER".equals(adOptions.position)) {
114
- mAdViewLayoutParams.setMargins(0, topInset, 0, 0);
115
- } else {
116
- mAdViewLayoutParams.setMargins(0, 0, 0, bottomInset);
117
- }
118
-
119
- mAdViewLayout.setLayoutParams(mAdViewLayoutParams);
120
- return insets;
121
- });
122
- }
123
-
124
107
  mAdViewLayout.setLayoutParams(mAdViewLayoutParams);
125
108
 
126
109
  int densityMargin = (int) (adOptions.margin * density);
110
+ int[] margins = new int[] { 0, densityMargin, 0, densityMargin };
127
111
 
128
112
  // Center Banner Ads
129
113
  int adWidth = (int) (adOptions.adSize.getSize().getWidth() * density);
@@ -133,15 +117,37 @@ public class BannerExecutor extends Executor {
133
117
  if (fullscreen) {
134
118
  margin = (realWidthPixels - defaultWidthPixels) / 2;
135
119
  }
120
+ margins[0] = margin;
121
+ margins[2] = margin;
136
122
  mAdViewLayoutParams.setMargins(margin, densityMargin, margin, densityMargin);
137
123
  } else {
138
124
  int sideMargin = ((int) defaultWidthPixels - adWidth) / 2;
139
125
  if (fullscreen) {
140
126
  sideMargin = (realWidthPixels - adWidth) / 2;
141
127
  }
128
+ margins[0] = sideMargin;
129
+ margins[2] = sideMargin;
142
130
  mAdViewLayoutParams.setMargins(sideMargin, densityMargin, sideMargin, densityMargin);
143
131
  }
144
132
 
133
+ // set Safe Area only for Android 15+
134
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
135
+ View rootView = activitySupplier.get().getWindow().getDecorView();
136
+ rootView.setOnApplyWindowInsetsListener((v, insets) -> {
137
+ int bottomInset = insets.getSystemWindowInsetBottom();
138
+ int topInset = insets.getSystemWindowInsetTop();
139
+
140
+ if ("TOP_CENTER".equals(adOptions.position)) {
141
+ mAdViewLayoutParams.setMargins(margins[0], margins[1] + topInset, margins[2], margins[3]);
142
+ } else {
143
+ mAdViewLayoutParams.setMargins(margins[0], margins[1], margins[2], margins[3] + bottomInset);
144
+ }
145
+
146
+ mAdViewLayout.setLayoutParams(mAdViewLayoutParams);
147
+ return insets;
148
+ });
149
+ }
150
+
145
151
  createNewAdView(adOptions);
146
152
 
147
153
  call.resolve();
@@ -223,11 +229,19 @@ public class BannerExecutor extends Executor {
223
229
  }
224
230
 
225
231
  private void updateExistingAdView(AdOptions adOptions) {
232
+ // Bind to the AdView present when this call was made. `mAdView` is a
233
+ // shared field that another UI-thread task can null before this one
234
+ // runs; using the captured reference avoids a NullPointerException.
235
+ final AdView adView = mAdView;
226
236
  activitySupplier
227
237
  .get()
228
238
  .runOnUiThread(() -> {
239
+ if (adView != mAdView) {
240
+ // Banner was removed or replaced before this task ran.
241
+ return;
242
+ }
229
243
  final AdRequest adRequest = RequestHelper.createRequest(adOptions);
230
- mAdView.loadAd(adRequest);
244
+ adView.loadAd(adRequest);
231
245
  });
232
246
  }
233
247
 
@@ -236,22 +250,36 @@ public class BannerExecutor extends Executor {
236
250
  * https://developers.google.com/admob/ios/banner?hl=ja
237
251
  */
238
252
  private void createNewAdView(AdOptions adOptions) {
253
+ // Bind to the AdView instance created for this call. `mAdView` is a
254
+ // shared field that removeBanner/hideBanner or a stale ad-listener
255
+ // callback can null from the UI thread before this posted task runs;
256
+ // reading the field inside the task would then throw a
257
+ // NullPointerException (e.g. in AdViewIdHelper.assignIdToAdView).
258
+ final AdView adView = mAdView;
259
+
239
260
  // Run AdMob In Main UI Thread
240
261
  activitySupplier
241
262
  .get()
242
263
  .runOnUiThread(() -> {
264
+ if (adView != mAdView) {
265
+ // Banner was removed or replaced before this task ran.
266
+ return;
267
+ }
243
268
  final AdRequest adRequest = RequestHelper.createRequest(adOptions);
244
269
  // Assign the correct id needed
245
- AdViewIdHelper.assignIdToAdView(mAdView, adOptions, adRequest, logTag, contextSupplier.get());
270
+ AdViewIdHelper.assignIdToAdView(adView, adOptions, adRequest, logTag, contextSupplier.get());
246
271
  // Add the AdView to the view hierarchy.
247
- mAdViewLayout.addView(mAdView);
272
+ mAdViewLayout.addView(adView);
248
273
  // Start loading the ad.
249
- mAdView.loadAd(adRequest);
250
- mAdView.setAdListener(
274
+ adView.loadAd(adRequest);
275
+ adView.setAdListener(
251
276
  new AdListener() {
252
277
  @Override
253
278
  public void onAdLoaded() {
254
- final BannerAdSizeInfo sizeInfo = new BannerAdSizeInfo(mAdView);
279
+ if (adView != mAdView) {
280
+ return;
281
+ }
282
+ final BannerAdSizeInfo sizeInfo = new BannerAdSizeInfo(adView);
255
283
 
256
284
  notifyListeners(BannerAdPluginEvents.SizeChanged.getWebEventName(), sizeInfo);
257
285
  notifyListeners(BannerAdPluginEvents.Loaded.getWebEventName(), emptyObject);
@@ -260,13 +288,19 @@ public class BannerExecutor extends Executor {
260
288
 
261
289
  @Override
262
290
  public void onAdFailedToLoad(@NonNull LoadAdError adError) {
263
- if (mAdView != null) {
264
- mViewGroup.removeView(mAdViewLayout);
265
- mAdViewLayout.removeView(mAdView);
266
- mAdView.destroy();
267
- mAdView = null;
291
+ if (adView != mAdView) {
292
+ // Stale callback from a banner that was already removed or
293
+ // replaced. Do not touch the current banner or emit teardown
294
+ // events for a view the JS layer has already discarded.
295
+ super.onAdFailedToLoad(adError);
296
+ return;
268
297
  }
269
298
 
299
+ mViewGroup.removeView(mAdViewLayout);
300
+ mAdViewLayout.removeView(adView);
301
+ adView.destroy();
302
+ mAdView = null;
303
+
270
304
  final BannerAdSizeInfo sizeInfo = new BannerAdSizeInfo(0, 0);
271
305
  notifyListeners(BannerAdPluginEvents.SizeChanged.getWebEventName(), sizeInfo);
272
306
 
@@ -296,6 +330,22 @@ public class BannerExecutor extends Executor {
296
330
  }
297
331
  );
298
332
 
333
+ adView.setOnPaidEventListener((adValue) -> {
334
+ if (adView != mAdView) {
335
+ return;
336
+ }
337
+ String networkName = "";
338
+ String impressionId = "";
339
+ if (adView.getResponseInfo() != null) {
340
+ networkName = adView.getResponseInfo().getMediationAdapterClassName();
341
+ if (networkName == null) networkName = "";
342
+ impressionId = adView.getResponseInfo().getResponseId();
343
+ if (impressionId == null) impressionId = "";
344
+ }
345
+ AdMobRevenueData revenueData = new AdMobRevenueData(adValue, adView.getAdUnitId(), networkName, impressionId);
346
+ notifyListeners(BannerAdPluginEvents.AdPaid.getWebEventName(), revenueData);
347
+ });
348
+
299
349
  // Add AdViewLayout top of the WebView
300
350
  mViewGroup.addView(mAdViewLayout);
301
351
  });
@@ -7,14 +7,18 @@ import com.google.android.gms.ads.AdError
7
7
  import com.google.android.gms.ads.FullScreenContentCallback
8
8
  import com.google.android.gms.common.util.BiConsumer
9
9
 
10
- class FullscreenPluginCallback(private val loadPluginObject: LoadPluginEventNames,
11
- private val notifyListenersFunction: BiConsumer<String, JSObject>): FullScreenContentCallback() {
10
+ class FullscreenPluginCallback(
11
+ private val loadPluginObject: LoadPluginEventNames,
12
+ private val notifyListenersFunction: BiConsumer<String, JSObject>,
13
+ private val onCompleted: Runnable? = null
14
+ ): FullScreenContentCallback() {
12
15
 
13
16
  override fun onAdShowedFullScreenContent() {
14
17
  notifyListenersFunction.accept(loadPluginObject.Showed, JSObject())
15
18
  }
16
19
 
17
20
  override fun onAdFailedToShowFullScreenContent(adError: AdError) {
21
+ onCompleted?.run()
18
22
  val adMobError = AdMobPluginError(adError)
19
23
  notifyListenersFunction.accept(
20
24
  loadPluginObject.FailedToShow, adMobError
@@ -22,6 +26,7 @@ class FullscreenPluginCallback(private val loadPluginObject: LoadPluginEventName
22
26
  }
23
27
 
24
28
  override fun onAdDismissedFullScreenContent() {
29
+ onCompleted?.run()
25
30
  notifyListenersFunction.accept(loadPluginObject.Dismissed, JSObject())
26
31
  }
27
32
  }