expo-ads 0.1.2 → 1.0.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 (178) hide show
  1. package/.editorconfig +5 -0
  2. package/android/build.gradle +32 -33
  3. package/android/src/main/AndroidManifest.xml +2 -2
  4. package/android/src/main/java/expo/modules/ads/meta/MetaAdsInitializer.kt +3 -1
  5. package/android/src/main/java/expo/modules/ads/meta/MetaAdsModule.kt +278 -69
  6. package/android/src/main/java/expo/modules/ads/meta/MetaBannerAdView.kt +77 -37
  7. package/android/src/main/java/expo/modules/ads/meta/MetaInterstitialManager.kt +130 -0
  8. package/android/src/main/java/expo/modules/ads/meta/MetaNativeAdManager.kt +146 -0
  9. package/android/src/main/java/expo/modules/ads/meta/MetaNativeAdView.kt +142 -0
  10. package/android/src/main/java/expo/modules/ads/meta/MetaNativeBannerAdType.kt +59 -0
  11. package/android/src/main/java/expo/modules/ads/meta/MetaNativeHostViews.kt +175 -0
  12. package/android/src/main/java/expo/modules/ads/meta/MetaRewardedInterstitialManager.kt +168 -0
  13. package/android/src/main/java/expo/modules/ads/meta/MetaRewardedManager.kt +168 -0
  14. package/build/meta/MetaAdEventType.d.ts +6 -0
  15. package/build/meta/MetaAdEventType.d.ts.map +1 -0
  16. package/build/meta/MetaAdEventType.js +2 -0
  17. package/build/meta/MetaAdEventType.js.map +1 -0
  18. package/build/meta/{ExpoAdsMetaModule.d.ts → MetaAdsModule.d.ts} +16 -3
  19. package/build/meta/MetaAdsModule.d.ts.map +1 -0
  20. package/build/meta/MetaAdsModule.js +3 -0
  21. package/build/meta/MetaAdsModule.js.map +1 -0
  22. package/build/meta/ads/BannerAd.d.ts +2 -2
  23. package/build/meta/ads/BannerAd.d.ts.map +1 -1
  24. package/build/meta/ads/BannerAd.js +9 -4
  25. package/build/meta/ads/BannerAd.js.map +1 -1
  26. package/build/meta/ads/InterstitialAd.d.ts +37 -0
  27. package/build/meta/ads/InterstitialAd.d.ts.map +1 -0
  28. package/build/meta/ads/InterstitialAd.js +99 -0
  29. package/build/meta/ads/InterstitialAd.js.map +1 -0
  30. package/build/meta/ads/RewardedAd.d.ts +55 -0
  31. package/build/meta/ads/RewardedAd.d.ts.map +1 -0
  32. package/build/meta/ads/RewardedAd.js +171 -0
  33. package/build/meta/ads/RewardedAd.js.map +1 -0
  34. package/build/meta/ads/RewardedInterstitialAd.d.ts +54 -0
  35. package/build/meta/ads/RewardedInterstitialAd.d.ts.map +1 -0
  36. package/build/meta/ads/RewardedInterstitialAd.js +170 -0
  37. package/build/meta/ads/RewardedInterstitialAd.js.map +1 -0
  38. package/build/meta/ads/native-ad/NativeAd.d.ts +59 -0
  39. package/build/meta/ads/native-ad/NativeAd.d.ts.map +1 -0
  40. package/build/meta/ads/native-ad/NativeAd.js +106 -0
  41. package/build/meta/ads/native-ad/NativeAd.js.map +1 -0
  42. package/build/meta/ads/native-ad/NativeAdView.d.ts +47 -0
  43. package/build/meta/ads/native-ad/NativeAdView.d.ts.map +1 -0
  44. package/build/meta/ads/native-ad/NativeAdView.js +58 -0
  45. package/build/meta/ads/native-ad/NativeAdView.js.map +1 -0
  46. package/build/meta/ads/native-ad/NativeAssets.d.ts +24 -0
  47. package/build/meta/ads/native-ad/NativeAssets.d.ts.map +1 -0
  48. package/build/meta/ads/native-ad/NativeAssets.js +28 -0
  49. package/build/meta/ads/native-ad/NativeAssets.js.map +1 -0
  50. package/build/meta/events/MetaInterstitialAdEventType.d.ts +15 -0
  51. package/build/meta/events/MetaInterstitialAdEventType.d.ts.map +1 -0
  52. package/build/meta/events/MetaInterstitialAdEventType.js +16 -0
  53. package/build/meta/events/MetaInterstitialAdEventType.js.map +1 -0
  54. package/build/meta/events/MetaNativeAdEventType.d.ts +12 -0
  55. package/build/meta/events/MetaNativeAdEventType.d.ts.map +1 -0
  56. package/build/meta/events/MetaNativeAdEventType.js +13 -0
  57. package/build/meta/events/MetaNativeAdEventType.js.map +1 -0
  58. package/build/meta/events/MetaRewardedAdEventType.d.ts +17 -0
  59. package/build/meta/events/MetaRewardedAdEventType.d.ts.map +1 -0
  60. package/build/meta/events/MetaRewardedAdEventType.js +18 -0
  61. package/build/meta/events/MetaRewardedAdEventType.js.map +1 -0
  62. package/build/meta/events/MetaRewardedInterstitialAdEventType.d.ts +13 -0
  63. package/build/meta/events/MetaRewardedInterstitialAdEventType.d.ts.map +1 -0
  64. package/build/meta/events/MetaRewardedInterstitialAdEventType.js +14 -0
  65. package/build/meta/events/MetaRewardedInterstitialAdEventType.js.map +1 -0
  66. package/build/meta/hooks/useInterstitialAd.d.ts +12 -0
  67. package/build/meta/hooks/useInterstitialAd.d.ts.map +1 -0
  68. package/build/meta/hooks/useInterstitialAd.js +48 -0
  69. package/build/meta/hooks/useInterstitialAd.js.map +1 -0
  70. package/build/meta/hooks/useNativeAd.d.ts +37 -0
  71. package/build/meta/hooks/useNativeAd.d.ts.map +1 -0
  72. package/build/meta/hooks/useNativeAd.js +50 -0
  73. package/build/meta/hooks/useNativeAd.js.map +1 -0
  74. package/build/meta/hooks/useRewardedAd.d.ts +16 -0
  75. package/build/meta/hooks/useRewardedAd.d.ts.map +1 -0
  76. package/build/meta/hooks/useRewardedAd.js +83 -0
  77. package/build/meta/hooks/useRewardedAd.js.map +1 -0
  78. package/build/meta/hooks/useRewardedInterstitialAd.d.ts +16 -0
  79. package/build/meta/hooks/useRewardedInterstitialAd.d.ts.map +1 -0
  80. package/build/meta/hooks/useRewardedInterstitialAd.js +83 -0
  81. package/build/meta/hooks/useRewardedInterstitialAd.js.map +1 -0
  82. package/build/meta/index.d.ts +22 -2
  83. package/build/meta/index.d.ts.map +1 -1
  84. package/build/meta/index.js +15 -3
  85. package/build/meta/index.js.map +1 -1
  86. package/build/meta/internal/ref.d.ts +10 -0
  87. package/build/meta/internal/ref.d.ts.map +1 -0
  88. package/build/meta/internal/ref.js +31 -0
  89. package/build/meta/internal/ref.js.map +1 -0
  90. package/build/meta/specs/components/{ExpoAdsMetaBannerViewNativeComponent.d.ts → MetaAdsBannerViewNativeComponent.d.ts} +3 -3
  91. package/build/meta/specs/components/MetaAdsBannerViewNativeComponent.d.ts.map +1 -0
  92. package/build/meta/specs/components/MetaAdsBannerViewNativeComponent.js +5 -0
  93. package/build/meta/specs/components/MetaAdsBannerViewNativeComponent.js.map +1 -0
  94. package/build/meta/specs/components/MetaAdsNativeViewNativeComponent.d.ts +23 -0
  95. package/build/meta/specs/components/MetaAdsNativeViewNativeComponent.d.ts.map +1 -0
  96. package/build/meta/specs/components/MetaAdsNativeViewNativeComponent.js +7 -0
  97. package/build/meta/specs/components/MetaAdsNativeViewNativeComponent.js.map +1 -0
  98. package/build/meta/types/MetaAdError.d.ts +2 -1
  99. package/build/meta/types/MetaAdError.d.ts.map +1 -1
  100. package/build/meta/types/MetaAdError.js.map +1 -1
  101. package/build/meta/types/MetaAdEventListener.d.ts +8 -0
  102. package/build/meta/types/MetaAdEventListener.d.ts.map +1 -0
  103. package/build/meta/types/MetaAdEventListener.js +2 -0
  104. package/build/meta/types/MetaAdEventListener.js.map +1 -0
  105. package/build/meta/types/MetaAdEventsListener.d.ts +7 -0
  106. package/build/meta/types/MetaAdEventsListener.d.ts.map +1 -0
  107. package/build/meta/types/MetaAdEventsListener.js +2 -0
  108. package/build/meta/types/MetaAdEventsListener.js.map +1 -0
  109. package/build/meta/types/MetaBannerAdProps.d.ts +6 -0
  110. package/build/meta/types/MetaBannerAdProps.d.ts.map +1 -1
  111. package/build/meta/types/MetaBannerAdProps.js.map +1 -1
  112. package/build/meta/types/MetaInterstitialEvent.d.ts +8 -0
  113. package/build/meta/types/MetaInterstitialEvent.d.ts.map +1 -0
  114. package/build/meta/types/MetaInterstitialEvent.js +2 -0
  115. package/build/meta/types/MetaInterstitialEvent.js.map +1 -0
  116. package/build/meta/types/MetaNativeAdProps.d.ts +55 -0
  117. package/build/meta/types/MetaNativeAdProps.d.ts.map +1 -0
  118. package/build/meta/types/MetaNativeAdProps.js +13 -0
  119. package/build/meta/types/MetaNativeAdProps.js.map +1 -0
  120. package/build/meta/types/MetaRewardedEvent.d.ts +19 -0
  121. package/build/meta/types/MetaRewardedEvent.d.ts.map +1 -0
  122. package/build/meta/types/MetaRewardedEvent.js +2 -0
  123. package/build/meta/types/MetaRewardedEvent.js.map +1 -0
  124. package/build/meta/types/MetaRewardedInterstitialEvent.d.ts +19 -0
  125. package/build/meta/types/MetaRewardedInterstitialEvent.d.ts.map +1 -0
  126. package/build/meta/types/MetaRewardedInterstitialEvent.js +2 -0
  127. package/build/meta/types/MetaRewardedInterstitialEvent.js.map +1 -0
  128. package/expo-module.config.json +6 -18
  129. package/package.json +79 -83
  130. package/src/meta/MetaAdEventType.ts +7 -0
  131. package/src/meta/{ExpoAdsMetaModule.ts → MetaAdsModule.ts} +70 -44
  132. package/src/meta/ads/BannerAd.tsx +12 -4
  133. package/src/meta/ads/InterstitialAd.ts +122 -0
  134. package/src/meta/ads/RewardedAd.ts +198 -0
  135. package/src/meta/ads/RewardedInterstitialAd.ts +206 -0
  136. package/src/meta/ads/native-ad/NativeAd.ts +142 -0
  137. package/src/meta/ads/native-ad/NativeAdView.tsx +88 -0
  138. package/src/meta/ads/native-ad/NativeAssets.tsx +52 -0
  139. package/src/meta/events/MetaInterstitialAdEventType.ts +14 -0
  140. package/src/meta/events/MetaNativeAdEventType.ts +13 -0
  141. package/src/meta/events/MetaRewardedAdEventType.ts +17 -0
  142. package/src/meta/events/MetaRewardedInterstitialAdEventType.ts +12 -0
  143. package/src/meta/hooks/useInterstitialAd.ts +54 -0
  144. package/src/meta/hooks/useNativeAd.ts +60 -0
  145. package/src/meta/hooks/useRewardedAd.ts +100 -0
  146. package/src/meta/hooks/useRewardedInterstitialAd.ts +102 -0
  147. package/src/meta/index.ts +42 -12
  148. package/src/meta/internal/ref.ts +37 -0
  149. package/src/meta/specs/components/{ExpoAdsMetaBannerViewNativeComponent.ts → MetaAdsBannerViewNativeComponent.ts} +6 -3
  150. package/src/meta/specs/components/MetaAdsNativeViewNativeComponent.ts +43 -0
  151. package/src/meta/types/MetaAdError.ts +8 -1
  152. package/src/meta/types/MetaAdEventListener.ts +15 -0
  153. package/src/meta/types/MetaAdEventsListener.ts +7 -0
  154. package/src/meta/types/MetaBannerAdProps.ts +7 -0
  155. package/src/meta/types/MetaInterstitialEvent.ts +8 -0
  156. package/src/meta/types/MetaNativeAdProps.ts +66 -0
  157. package/src/meta/types/MetaRewardedEvent.ts +20 -0
  158. package/src/meta/types/MetaRewardedInterstitialEvent.ts +20 -0
  159. package/tsconfig.json +1 -1
  160. package/android/src/main/java/expo/modules/ads/google/GoogleAdsModule.kt +0 -17
  161. package/build/google/ExpoAdsGoogleModule.d.ts +0 -6
  162. package/build/google/ExpoAdsGoogleModule.d.ts.map +0 -1
  163. package/build/google/ExpoAdsGoogleModule.js +0 -3
  164. package/build/google/ExpoAdsGoogleModule.js.map +0 -1
  165. package/build/google/index.d.ts +0 -2
  166. package/build/google/index.d.ts.map +0 -1
  167. package/build/google/index.js +0 -6
  168. package/build/google/index.js.map +0 -1
  169. package/build/meta/ExpoAdsMetaModule.d.ts.map +0 -1
  170. package/build/meta/ExpoAdsMetaModule.js +0 -3
  171. package/build/meta/ExpoAdsMetaModule.js.map +0 -1
  172. package/build/meta/specs/components/ExpoAdsMetaBannerViewNativeComponent.d.ts.map +0 -1
  173. package/build/meta/specs/components/ExpoAdsMetaBannerViewNativeComponent.js +0 -5
  174. package/build/meta/specs/components/ExpoAdsMetaBannerViewNativeComponent.js.map +0 -1
  175. package/ios/Expo-ads.podspec +0 -23
  176. package/ios/Expo-adsModule.swift +0 -7
  177. package/src/google/ExpoAdsGoogleModule.ts +0 -6
  178. package/src/google/index.ts +0 -5
package/.editorconfig ADDED
@@ -0,0 +1,5 @@
1
+ root = true
2
+
3
+ [*.{kt,kts}]
4
+ indent_style = space
5
+ indent_size = 2
@@ -1,34 +1,33 @@
1
- import groovy.json.JsonSlurper
2
-
3
- plugins {
4
- id 'com.android.library'
5
- id 'expo-module-gradle-plugin'
6
- }
7
-
8
- group = 'expo.modules.ads'
9
- version = '0.1.1'
10
-
11
- def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text)
12
-
13
- def metaAudienceNetworkVersion = packageJson.sdkVersions.android.metaAudienceNetwork
14
- def playServicesAdsIdentifierVersion = packageJson.sdkVersions.android.playServicesAdsIdentifier
15
- def playServicesBasementVersion = packageJson.sdkVersions.android.playServicesBasement
16
-
17
- android {
18
- namespace "expo.modules.ads"
19
- defaultConfig {
20
- versionCode 1
21
- versionName "0.1.1"
22
- }
23
- lintOptions {
24
- abortOnError false
25
- }
26
- }
27
-
28
-
29
-
30
- dependencies {
31
- implementation("com.facebook.android:audience-network-sdk:${metaAudienceNetworkVersion}")
32
- implementation("com.google.android.gms:play-services-ads-identifier:${playServicesAdsIdentifierVersion}")
33
- implementation("com.google.android.gms:play-services-basement:${playServicesBasementVersion}")
1
+ import groovy.json.JsonSlurper
2
+
3
+ plugins {
4
+ id 'com.android.library'
5
+ id 'expo-module-gradle-plugin'
6
+ }
7
+
8
+ group = 'expo.modules.ads'
9
+ version = '0.1.3'
10
+
11
+ def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text)
12
+
13
+ def metaAudienceNetworkVersion = packageJson.sdkVersions.android.metaAudienceNetwork
14
+ def playServicesAdsIdentifierVersion = packageJson.sdkVersions.android.playServicesAdsIdentifier
15
+ def playServicesBasementVersion = packageJson.sdkVersions.android.playServicesBasement
16
+
17
+ android {
18
+ namespace "expo.modules.ads"
19
+ defaultConfig {
20
+ versionCode 1
21
+ versionName "0.1.3"
22
+ }
23
+ lintOptions {
24
+ abortOnError false
25
+ }
26
+ }
27
+
28
+
29
+ dependencies {
30
+ implementation("com.facebook.android:audience-network-sdk:${metaAudienceNetworkVersion}")
31
+ implementation("com.google.android.gms:play-services-ads-identifier:${playServicesAdsIdentifierVersion}")
32
+ implementation("com.google.android.gms:play-services-basement:${playServicesBasementVersion}")
34
33
  }
@@ -1,2 +1,2 @@
1
- <manifest>
2
- </manifest>
1
+ <manifest>
2
+ </manifest>
@@ -10,7 +10,7 @@ import com.facebook.ads.AudienceNetworkAds
10
10
 
11
11
  object MetaAdsInitializer : AudienceNetworkAds.InitListener {
12
12
 
13
- const val TAG = "ExpoAdsMeta"
13
+ const val TAG = "MetaAds"
14
14
 
15
15
  private enum class State { IDLE, INITIALIZING, READY, FAILED }
16
16
 
@@ -21,6 +21,8 @@ object MetaAdsInitializer : AudienceNetworkAds.InitListener {
21
21
 
22
22
  fun isReady(): Boolean = synchronized(lock) { state == State.READY }
23
23
 
24
+ fun clearPending() = synchronized(lock) { pending.clear() }
25
+
24
26
  fun whenReady(context: Context, callback: (success: Boolean, message: String) -> Unit) {
25
27
  val appContext = context.applicationContext
26
28
  var startInit = false
@@ -7,87 +7,296 @@ import expo.modules.kotlin.modules.Module
7
7
  import expo.modules.kotlin.modules.ModuleDefinition
8
8
 
9
9
  class MetaAdsModule : Module() {
10
- override fun definition() = ModuleDefinition {
11
- Name("ExpoAdsMeta") // the name JS uses to find this module (import from "expo-ads/meta")
12
-
13
- // Runs automatically when the module loads (warm-up)
14
- OnCreate {
15
- val ctx = appContext.reactContext
16
- if (ctx == null) {
17
- Log.w(MetaAdsInitializer.TAG, "[Module] OnCreate: React context is null, skipping warm-up")
18
- } else {
19
- Log.d(MetaAdsInitializer.TAG, "[Module] OnCreate: starting SDK warm-up")
20
- MetaAdsInitializer.whenReady(ctx) { _, _ -> }
10
+ init {
11
+ Log.d("MetaAds", "🔥 MetaAdsModule INSTANCE CREATED")
12
+ }
13
+
14
+ override fun definition() =
15
+ ModuleDefinition {
16
+ Name("MetaAds") // the name JS uses to find this module (import from "expo-ads/meta")
17
+
18
+ // Runs automatically when the module loads (warm-up)
19
+ OnCreate {
20
+ val ctx = appContext.reactContext
21
+ if (ctx == null) {
22
+ Log.w(MetaAdsInitializer.TAG, "[Module] OnCreate: React context is null, skipping warm-up")
23
+ } else {
24
+ Log.d(MetaAdsInitializer.TAG, "[Module] OnCreate: starting SDK warm-up")
25
+ MetaAdsInitializer.whenReady(ctx) { _, _ -> }
26
+ }
21
27
  }
22
- }
23
28
 
24
- // Lets JS call: await ExpoAdsMeta.initialize()
25
- AsyncFunction("initialize") { promise: Promise ->
26
- val ctx = appContext.reactContext
27
- if (ctx == null) {
28
- Log.e(MetaAdsInitializer.TAG, "[Module] initialize() called from JS but React context is null")
29
- promise.reject("ERR_NO_CONTEXT", "React context not available", null)
30
- return@AsyncFunction
29
+ OnDestroy {
30
+ MetaAdsInitializer.clearPending()
31
+ MetaInterstitialManager.destroyAll()
32
+ MetaRewardedInterstitialManager.destroyAll()
31
33
  }
32
- Log.d(MetaAdsInitializer.TAG, "[Module] initialize() called from JS")
33
- MetaAdsInitializer.whenReady(ctx) { ok, msg ->
34
- if (ok) promise.resolve(msg) else promise.reject("ERR_META_INIT", msg, null)
34
+
35
+ Events("onInterstitialEvent", "onRewardedEvent", "onRewardedInterstitialEvent")
36
+
37
+ // Lets JS call: await MetaAds.initialize()
38
+ AsyncFunction("initialize") { promise: Promise ->
39
+ val ctx = appContext.reactContext
40
+ if (ctx == null) {
41
+ Log.e(MetaAdsInitializer.TAG, "[Module] initialize() called from JS but React context is null")
42
+ promise.reject("ERR_NO_CONTEXT", "React context not available", null)
43
+ return@AsyncFunction
44
+ }
45
+ Log.d(MetaAdsInitializer.TAG, "[Module] initialize() called from JS")
46
+ MetaAdsInitializer.whenReady(ctx) { ok, msg ->
47
+ if (ok) promise.resolve(msg) else promise.reject("ERR_META_INIT", msg, null)
48
+ }
35
49
  }
36
- }
37
50
 
38
- // Lets JS call: await ExpoAdsMeta.setTestMode(true)
39
- //
40
- // This is a plain static AdSettings toggle with no I/O, so it's a synchronous Function
41
- // rather than an AsyncFunction — but it's still safe (and expected) to `await` it from
42
- // JS, since every native function on this module returns a Promise from the JS side
43
- // regardless of whether it's sync or async natively.
44
- //
45
- // Deliberately NOT gated on MetaAdsInitializer.isReady(): Meta's own docs call this
46
- // (and addTestDevices) *before* the SDK finishes async initialization — test mode and
47
- // test devices must be registered before the first loadAd() call, not after init.
48
- Function("setTestMode") { enabled: Boolean ->
49
- Log.d(MetaAdsInitializer.TAG, "[Module] setTestMode($enabled)")
50
- AdSettings.setTestMode(enabled)
51
- }
51
+ // Lets JS call: await MetaAds.setTestMode(true)
52
+ //
53
+ // This is a plain static AdSettings toggle with no I/O, so it's a synchronous Function
54
+ // rather than an AsyncFunction — but it's still safe (and expected) to `await` it from
55
+ // JS, since every native function on this module returns a Promise from the JS side
56
+ // regardless of whether it's sync or async natively.
57
+ //
58
+ // Deliberately NOT gated on MetaAdsInitializer.isReady(): Meta's own docs call this
59
+ // (and addTestDevices) *before* the SDK finishes async initialization — test mode and
60
+ // test devices must be registered before the first loadAd() call, not after init.
61
+ Function("setTestMode") { enabled: Boolean ->
62
+ Log.d(MetaAdsInitializer.TAG, "[Module] setTestMode($enabled)")
63
+ AdSettings.setTestMode(enabled)
64
+ }
52
65
 
53
- // Lets JS call: await ExpoAdsMeta.addTestDevices(["HASHED_ID_1", "HASHED_ID_2"])
54
- //
55
- // Accepts a list so a single call can register every test device hash at once; each
56
- // hash is normally copied from Logcat the first time a real ad request is made on that
57
- // device (see MetaAdsInitializer's warm-up call in OnCreate above, or any loadAd()).
58
- Function("addTestDevices") { deviceHashes: List<String> ->
59
- Log.d(MetaAdsInitializer.TAG, "[Module] addTestDevices(${deviceHashes.size} device(s))")
60
- AdSettings.addTestDevices(deviceHashes)
61
- }
66
+ // Lets JS call: await MetaAds.addTestDevices(["HASHED_ID_1", "HASHED_ID_2"])
67
+ //
68
+ // Accepts a list so a single call can register every test device hash at once; each
69
+ // hash is normally copied from Logcat the first time a real ad request is made on that
70
+ // device (see MetaAdsInitializer's warm-up call in OnCreate above, or any loadAd()).
71
+ Function("addTestDevices") { deviceHashes: List<String> ->
72
+ Log.d(MetaAdsInitializer.TAG, "[Module] addTestDevices(${deviceHashes.size} device(s))")
73
+ AdSettings.addTestDevices(deviceHashes)
74
+ }
62
75
 
63
- // Lets JS call: await ExpoAdsMeta.clearTestDevices()
64
- //
65
- // Mirrors Meta's documented "before shipping" cleanup step
66
- // (AdSettings.setTestMode(false); AdSettings.clearTestDevices();). Exposed separately
67
- // from setTestMode(false) so callers can clear registered device hashes independently.
68
- Function("clearTestDevices") {
69
- Log.d(MetaAdsInitializer.TAG, "[Module] clearTestDevices()")
70
- AdSettings.clearTestDevices()
71
- }
76
+ // Lets JS call: await MetaAds.clearTestDevices()
77
+ //
78
+ // Mirrors Meta's documented "before shipping" cleanup step
79
+ // (AdSettings.setTestMode(false); AdSettings.clearTestDevices();). Exposed separately
80
+ // from setTestMode(false) so callers can clear registered device hashes independently.
81
+ Function("clearTestDevices") {
82
+ Log.d(MetaAdsInitializer.TAG, "[Module] clearTestDevices()")
83
+ AdSettings.clearTestDevices()
84
+ }
85
+
86
+ AsyncFunction("interstitialLoad") { requestId: Int, unitId: String ->
87
+ val emit: InterstitialEmitter = { body ->
88
+ try {
89
+ sendEvent("onInterstitialEvent", body)
90
+ } catch (
91
+ e: Exception,
92
+ ) {
93
+ Log.w(MetaAdsInitializer.TAG, "[Interstitial] sendEvent failed: ${e.message}")
94
+ }
95
+ }
96
+ val ctx = appContext.currentActivity ?: appContext.reactContext
97
+ if (ctx == null) {
98
+ emit(
99
+ mapOf(
100
+ "requestId" to requestId,
101
+ "type" to "error",
102
+ "code" to "ERR_NO_CONTEXT",
103
+ "message" to "No Activity/React context available",
104
+ ),
105
+ )
106
+ return@AsyncFunction
107
+ }
108
+ MetaInterstitialManager.runOnMain {
109
+ MetaInterstitialManager.load(ctx, requestId, unitId, emit)
110
+ }
111
+ }
72
112
 
73
- // Banner ad native view, exposed to JS as <ExpoAdsMetaBannerView>.
74
- View(MetaBannerAdView::class) {
75
- Events("onAdLoaded", "onAdFailedToLoad", "onAdClicked", "onLoggingImpression")
113
+ AsyncFunction("interstitialShow") { requestId: Int, promise: Promise ->
114
+ MetaInterstitialManager.runOnMain {
115
+ val failure = MetaInterstitialManager.show(requestId)
116
+ if (failure == null) {
117
+ promise.resolve(null)
118
+ } else {
119
+ promise.reject(failure.first, failure.second, null)
120
+ }
121
+ }
122
+ }
123
+
124
+ Function("interstitialDestroy") { requestId: Int ->
125
+ MetaInterstitialManager.runOnMain { MetaInterstitialManager.destroy(requestId) }
126
+ }
76
127
 
77
- Prop("unitId") { view: MetaBannerAdView, value: String? ->
78
- view.setUnitId(value)
128
+ AsyncFunction("rewardedLoad") { requestId: Int, unitId: String, userId: String?, currency: String? ->
129
+ val emit: RewardedEmitter = { body ->
130
+ try {
131
+ sendEvent("onRewardedEvent", body)
132
+ } catch (e: Exception) {
133
+ Log.w(MetaAdsInitializer.TAG, "[Rewarded] sendEvent failed: ${e.message}")
134
+ }
135
+ }
136
+ val ctx = appContext.currentActivity ?: appContext.reactContext
137
+ if (ctx == null) {
138
+ emit(
139
+ mapOf(
140
+ "requestId" to requestId,
141
+ "type" to "error",
142
+ "code" to "ERR_NO_CONTEXT",
143
+ "message" to "No Activity/React context available",
144
+ ),
145
+ )
146
+ return@AsyncFunction
147
+ }
148
+ MetaRewardedManager.runOnMain {
149
+ MetaRewardedManager.load(ctx, requestId, unitId, userId, currency, emit)
150
+ }
79
151
  }
80
152
 
81
- Prop("size") { view: MetaBannerAdView, value: MetaBannerAdSize ->
82
- view.setSize(value)
153
+ AsyncFunction("rewardedShow") { requestId: Int, promise: Promise ->
154
+ val emit: RewardedEmitter = { body ->
155
+ try {
156
+ sendEvent("onRewardedEvent", body)
157
+ } catch (e: Exception) {
158
+ Log.w(MetaAdsInitializer.TAG, "[Rewarded] sendEvent failed: ${e.message}")
159
+ }
160
+ }
161
+ MetaRewardedManager.runOnMain {
162
+ val failure = MetaRewardedManager.show(requestId, emit)
163
+ if (failure == null) promise.resolve(null) else promise.reject(failure.first, failure.second, null)
164
+ }
83
165
  }
84
166
 
85
- // Fires after unitId/size are applied for this commit, mirroring the
86
- // onAfterUpdateTransaction "props changed -> (re)request" pattern used by the reference
87
- // Google Mobile Ads ViewManager, but expressed as an Expo Modules view lifecycle hook.
88
- OnViewDidUpdateProps { view: MetaBannerAdView ->
89
- view.requestAd()
167
+ AsyncFunction("rewardedIsValid") { requestId: Int, promise: Promise ->
168
+ MetaRewardedManager.runOnMain { promise.resolve(MetaRewardedManager.isValid(requestId)) }
169
+ }
170
+
171
+ Function("rewardedDestroy") { requestId: Int ->
172
+ MetaRewardedManager.runOnMain { MetaRewardedManager.destroy(requestId) }
173
+ }
174
+
175
+ AsyncFunction("rewardedInterstitialLoad") { requestId: Int, unitId: String, userId: String?, currency: String? ->
176
+ val emit: RewardedInterstitialEmitter = { body ->
177
+ try {
178
+ sendEvent("onRewardedInterstitialEvent", body)
179
+ } catch (
180
+ e: Exception,
181
+ ) {
182
+ Log.w(MetaAdsInitializer.TAG, "[RewardedInterstitial] sendEvent failed: ${e.message}")
183
+ }
184
+ }
185
+ val ctx = appContext.currentActivity ?: appContext.reactContext
186
+ if (ctx == null) {
187
+ emit(
188
+ mapOf(
189
+ "requestId" to requestId,
190
+ "type" to "error",
191
+ "code" to "ERR_NO_CONTEXT",
192
+ "message" to "No Activity/React context available",
193
+ ),
194
+ )
195
+ return@AsyncFunction
196
+ }
197
+ MetaRewardedInterstitialManager.runOnMain {
198
+ MetaRewardedInterstitialManager.load(ctx, requestId, unitId, userId, currency, emit)
199
+ }
200
+ }
201
+
202
+ AsyncFunction("rewardedInterstitialShow") { requestId: Int, promise: Promise ->
203
+ val emit: RewardedInterstitialEmitter = { body ->
204
+ try {
205
+ sendEvent("onRewardedInterstitialEvent", body)
206
+ } catch (
207
+ e: Exception,
208
+ ) {
209
+ Log.w(MetaAdsInitializer.TAG, "[RewardedInterstitial] sendEvent failed: ${e.message}")
210
+ }
211
+ }
212
+ MetaRewardedInterstitialManager.runOnMain {
213
+ val failure = MetaRewardedInterstitialManager.show(requestId, emit)
214
+ if (failure == null) promise.resolve(null) else promise.reject(failure.first, failure.second, null)
215
+ }
216
+ }
217
+
218
+ AsyncFunction("rewardedInterstitialIsValid") { requestId: Int, promise: Promise ->
219
+ MetaRewardedInterstitialManager.runOnMain { promise.resolve(MetaRewardedInterstitialManager.isValid(requestId)) }
220
+ }
221
+
222
+ Function("rewardedInterstitialDestroy") { requestId: Int ->
223
+ MetaRewardedInterstitialManager.runOnMain { MetaRewardedInterstitialManager.destroy(requestId) }
224
+ }
225
+
226
+ // Banner ad native view, exposed to JS as <MetaBannerAdView>.
227
+ View(MetaBannerAdView::class) {
228
+ Events("onAdLoaded", "onAdFailedToLoad", "onAdClicked", "onLoggingImpression")
229
+
230
+ Prop("unitId") { view: MetaBannerAdView, value: String? ->
231
+ view.setUnitId(value)
232
+ }
233
+
234
+ Prop("size") { view: MetaBannerAdView, value: MetaBannerAdSize ->
235
+ view.setSize(value)
236
+ }
237
+
238
+ // Fires after unitId/size are applied for this commit, mirroring the
239
+ // onAfterUpdateTransaction "props changed -> (re)request" pattern used by the reference
240
+ // Google Mobile Ads ViewManager, but expressed as an Expo Modules view lifecycle hook.
241
+ OnViewDidUpdateProps { view: MetaBannerAdView -> view.requestAd() }
242
+
243
+ OnViewDestroys { view: MetaBannerAdView -> view.cleanup() }
244
+ }
245
+
246
+ AsyncFunction("nativeAdLoad") { unitId: String, promise: Promise ->
247
+ val ctx = appContext.currentActivity ?: appContext.reactContext
248
+ if (ctx == null) {
249
+ promise.reject("ERR_NO_CONTEXT", "No Activity/React context available", null)
250
+ return@AsyncFunction
251
+ }
252
+ MetaNativeAdManager.runOnMain {
253
+ MetaNativeAdManager.load(
254
+ ctx,
255
+ unitId,
256
+ emit = { body ->
257
+ try {
258
+ sendEvent("onNativeAdEvent", body)
259
+ } catch (
260
+ e: Exception,
261
+ ) {
262
+ Log.w(MetaAdsInitializer.TAG, "[Native] sendEvent failed: ${e.message}")
263
+ }
264
+ },
265
+ onLoaded = { promise.resolve(it) },
266
+ onFailed = { code, msg -> promise.reject(code, msg, null) },
267
+ )
268
+ }
269
+ }
270
+
271
+ Function("nativeAdDestroy") { adId: String ->
272
+ MetaNativeAdManager.runOnMain { MetaNativeAdManager.destroy(adId) }
273
+ }
274
+
275
+ View(MetaNativeAdView::class) {
276
+ Prop("adId") { view: MetaNativeAdView, value: String? -> view.setAdId(value) }
277
+
278
+ AsyncFunction("registerAsset") { view: MetaNativeAdView, assetType: String, reactTag: Int ->
279
+ view.registerAsset(assetType, reactTag)
280
+ }
281
+
282
+ GroupView<MetaNativeAdView> {
283
+ AddChildView { parent: MetaNativeAdView, child: android.view.View, index: Int ->
284
+ parent.addChild(child, index)
285
+ }
286
+ GetChildCount { parent -> parent.childCount() }
287
+ GetChildViewAt { parent, index -> parent.childAt(index) }
288
+ RemoveChildViewAt { parent, index -> parent.removeChildAt(index) }
289
+ }
290
+
291
+ OnViewDestroys { view: MetaNativeAdView -> view.cleanup() }
292
+ }
293
+ View(MetaNativeMediaView::class) {}
294
+ View(MetaNativeIconView::class) {}
295
+ View(MetaNativeAdOptionsView::class) {
296
+ Prop("orientation") { view: MetaNativeAdOptionsView, v: String? -> view.setOrientation(v) }
297
+ Prop("iconSize") { view: MetaNativeAdOptionsView, v: Int? -> view.setIconSize(v) }
298
+ Prop("iconColor") { view: MetaNativeAdOptionsView, v: Int? -> view.setIconColor(v) }
299
+ OnViewDidUpdateProps { view: MetaNativeAdOptionsView -> view.rebuild() }
90
300
  }
91
301
  }
92
- }
93
- }
302
+ }
@@ -18,64 +18,68 @@ class MetaBannerAdView(context: Context, appContext: AppContext) : ExpoView(cont
18
18
  private var adView: AdView? = null
19
19
  private var unitId: String? = null
20
20
  private var size: MetaBannerAdSize = MetaBannerAdSize.BANNER_HEIGHT_50
21
+ private var loadedUnitId: String? = null
22
+ private var loadedSize: MetaBannerAdSize? = null
23
+ private var destroyed = false
21
24
 
22
25
  private val onAdLoaded by EventDispatcher()
23
26
  private val onAdFailedToLoad by EventDispatcher()
24
27
  private val onAdClicked by EventDispatcher()
25
28
  private val onLoggingImpression by EventDispatcher()
26
29
 
27
- fun setUnitId(value: String?) {
28
- unitId = value
29
- }
30
+ fun setUnitId(value: String?) { unitId = value }
31
+ fun setSize(value: MetaBannerAdSize) { size = value }
30
32
 
31
- fun setSize(value: MetaBannerAdSize) {
32
- size = value
33
+ // Swallows dispatches to a dead React context instead of crashing
34
+ private fun safeDispatch(block: () -> Unit) {
35
+ if (destroyed) return
36
+ try {
37
+ block()
38
+ } catch (e: Exception) {
39
+ Log.w(TAG, "[Banner] event dispatch failed: ${e.message}")
40
+ }
33
41
  }
34
42
 
35
- /**
36
- * Called from `OnViewDidUpdateProps` in MetaAdsModule after unitId/size are applied for a
37
- * commit (mirroring the "props changed -> (re)request" pattern from the reference Google
38
- * Mobile Ads ViewManager's onAfterUpdateTransaction). Runs the two required guards, in
39
- * order, before ever touching the SDK:
40
- * 1. Ad unit must be configured — otherwise don't build a request at all.
41
- * 2. SDK must already report READY — otherwise tell the caller to initialize first.
42
- * Neither failure throws; both are surfaced to JS as onAdFailedToLoad so callers can
43
- * handle them the same way they'd handle any other load failure.
44
- */
45
43
  fun requestAd() {
44
+ if (destroyed) return
46
45
  val currentUnitId = unitId
47
46
 
48
47
  if (currentUnitId.isNullOrBlank()) {
49
48
  val message = "Meta banner ad request skipped: no adUnitId was provided. Set the " +
50
49
  "`unitId` prop on <MetaBannerAd> to a valid Audience Network placement ID."
51
50
  Log.e(TAG, "[Banner] $message")
52
- onAdFailedToLoad(mapOf("code" to "ERR_NO_AD_UNIT", "message" to message))
51
+ safeDispatch { onAdFailedToLoad(mapOf("code" to "ERR_NO_AD_UNIT", "message" to message)) }
53
52
  return
54
53
  }
55
54
 
56
55
  if (!MetaAdsInitializer.isReady()) {
57
56
  val message = "Meta banner ad request skipped: Audience Network SDK is not initialized " +
58
- "yet. Call ExpoAdsMeta.initialize() and wait for it to resolve before requesting a " +
57
+ "yet. Call MetaAds.initialize() and wait for it to resolve before requesting a " +
59
58
  "banner ad."
60
59
  Log.e(TAG, "[Banner] $message")
61
- onAdFailedToLoad(mapOf("code" to "ERR_NOT_INITIALIZED", "message" to message))
60
+ safeDispatch { onAdFailedToLoad(mapOf("code" to "ERR_NOT_INITIALIZED", "message" to message)) }
62
61
  return
63
62
  }
64
63
 
64
+ // Skip redundant reloads when nothing changed
65
+ if (adView != null && loadedUnitId == currentUnitId && loadedSize == size) return
66
+
65
67
  loadAd(currentUnitId, size)
66
68
  }
67
69
 
68
70
  private fun loadAd(currentUnitId: String, currentSize: MetaBannerAdSize) {
69
- // Tear down any previous ad view before creating a new one (unit or size may have changed).
70
71
  destroyAdView()
72
+ loadedUnitId = currentUnitId
73
+ loadedSize = currentSize
71
74
 
72
75
  val newAdView = AdView(context, currentUnitId, currentSize.toFacebookAdSize())
73
76
  adView = newAdView
74
77
 
75
- newAdView.setAdListener(
76
- object : AdListener {
77
- override fun onError(ad: Ad, adError: AdError) {
78
- Log.e(TAG, "[Banner] onError: ${adError.errorCode} ${adError.errorMessage}")
78
+ val adListener = object : AdListener {
79
+ override fun onError(ad: Ad, adError: AdError) {
80
+ if (adView !== newAdView) return // stale callback from a replaced ad
81
+ Log.e(TAG, "[Banner] onError: ${adError.errorCode} ${adError.errorMessage}")
82
+ safeDispatch {
79
83
  onAdFailedToLoad(
80
84
  mapOf(
81
85
  "code" to adError.errorCode.toString(),
@@ -83,9 +87,12 @@ class MetaBannerAdView(context: Context, appContext: AppContext) : ExpoView(cont
83
87
  )
84
88
  )
85
89
  }
90
+ }
86
91
 
87
- override fun onAdLoaded(ad: Ad) {
88
- Log.d(TAG, "[Banner] onAdLoaded")
92
+ override fun onAdLoaded(ad: Ad) {
93
+ if (adView !== newAdView) return
94
+ Log.d(TAG, "[Banner] onAdLoaded")
95
+ safeDispatch {
89
96
  onAdLoaded(
90
97
  mapOf(
91
98
  "width" to currentSize.toFacebookAdSize().width,
@@ -93,35 +100,68 @@ class MetaBannerAdView(context: Context, appContext: AppContext) : ExpoView(cont
93
100
  )
94
101
  )
95
102
  }
96
-
97
- override fun onAdClicked(ad: Ad) {
98
- onAdClicked(emptyMap())
99
- }
100
-
101
- override fun onLoggingImpression(ad: Ad) {
102
- onLoggingImpression(emptyMap())
103
+ // Force a layout pass so RN shows the natively-added child
104
+ post {
105
+ measure(
106
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
107
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
108
+ )
109
+ layout(left, top, right, bottom)
103
110
  }
104
111
  }
105
- )
112
+
113
+ override fun onAdClicked(ad: Ad) = safeDispatch { onAdClicked(emptyMap()) }
114
+
115
+ override fun onLoggingImpression(ad: Ad) = safeDispatch { onLoggingImpression(emptyMap()) }
116
+ }
106
117
 
107
118
  addView(
108
119
  newAdView,
109
120
  ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
110
121
  )
111
- newAdView.loadAd()
122
+
123
+ newAdView.loadAd(newAdView.buildLoadAdConfig().withAdListener(adListener).build())
112
124
  }
113
125
 
114
126
  private fun destroyAdView() {
115
127
  adView?.let {
116
- it.setAdListener(null)
117
- removeView(it)
118
- it.destroy()
128
+ try {
129
+ removeView(it)
130
+ it.destroy()
131
+ } catch (e: Exception) {
132
+ Log.w(TAG, "[Banner] destroy failed: ${e.message}")
133
+ }
119
134
  }
120
135
  adView = null
136
+ loadedUnitId = null
137
+ loadedSize = null
138
+ }
139
+
140
+ // Called from OnViewDestroys in MetaAdsModule when React drops this view
141
+ fun cleanup() {
142
+ destroyed = true
143
+ destroyAdView()
144
+ }
145
+
146
+ override fun onAttachedToWindow() {
147
+ super.onAttachedToWindow()
148
+ destroyed = false
121
149
  }
122
150
 
123
151
  override fun onDetachedFromWindow() {
124
152
  destroyAdView()
125
153
  super.onDetachedFromWindow()
126
154
  }
155
+
156
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
157
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
158
+ adView?.measure(
159
+ MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
160
+ MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST)
161
+ )
162
+ }
163
+
164
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
165
+ adView?.let { it.layout(0, 0, it.measuredWidth, it.measuredHeight) }
166
+ }
127
167
  }