@advergic-ads/react-native-sdk 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 (29) hide show
  1. package/AdvergicReactNative.podspec +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +329 -0
  4. package/android/advergic-bridge/build.gradle +58 -0
  5. package/android/advergic-bridge/libs/advergic-sdk.aar +0 -0
  6. package/android/advergic-bridge/src/main/java/com/advergic/reactnative/AdvergicBannerViewManager.kt +194 -0
  7. package/android/advergic-bridge/src/main/java/com/advergic/reactnative/AdvergicReactNativeModule.java +401 -0
  8. package/android/advergic-bridge/src/main/java/com/advergic/reactnative/AdvergicReactNativePackage.java +25 -0
  9. package/android/app/build.gradle +144 -0
  10. package/android/app/libs/advergic-sdk.aar +0 -0
  11. package/android/app/src/main/AndroidManifest.xml +30 -0
  12. package/android/app/src/main/java/com/advergic/reactnative/AdvergicReactNativePackage.java +26 -0
  13. package/android/app/src/main/java/com/facebook/react/viewmanagers/AdvergicBannerViewManagerInterface.java +8 -0
  14. package/android/libs/advergic-sdk.aar +0 -0
  15. package/android/settings.gradle +7 -0
  16. package/index.d.ts +135 -0
  17. package/index.js +27 -0
  18. package/install.js +158 -0
  19. package/ios/AdvergicBannerView.swift +59 -0
  20. package/ios/AdvergicBannerViewManager.m +18 -0
  21. package/ios/AdvergicBannerViewManager.swift +24 -0
  22. package/ios/AdvergicReactNativeModule.m +36 -0
  23. package/ios/AdvergicReactNativeModule.swift +363 -0
  24. package/package.json +57 -0
  25. package/react-native.config.js +18 -0
  26. package/src/advergic/AdvergicBannerAd.tsx +51 -0
  27. package/src/advergic/AdvergicSDK.ts +318 -0
  28. package/src/advergic/index.ts +11 -0
  29. package/src/advergic/types.ts +23 -0
@@ -0,0 +1,32 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "AdvergicReactNative"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.author = package["author"]
12
+ s.platforms = { :ios => "15.0" }
13
+ s.source = { :git => "https://github.com/advergic/react-native-sdk.git", :tag => "#{s.version}" }
14
+
15
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
16
+ s.swift_version = "5.0"
17
+ s.requires_arc = true
18
+
19
+ # React Native core
20
+ s.dependency "React-Core"
21
+
22
+ # Native Advergic iOS SDK. It is NOT on the CocoaPods trunk — it is published
23
+ # via a podspec hosted at app.advergic.com (which always serves the latest
24
+ # version). The consuming app must therefore add the following line to its
25
+ # Podfile so CocoaPods can resolve this dependency:
26
+ #
27
+ # pod 'AdvergicSDK', :podspec => 'https://app.advergic.com/ios/sdk/AdvergicSDK.podspec'
28
+ #
29
+ # A `>=` floor (not `~>`) is used so future releases served at that single,
30
+ # always-latest podspec URL keep resolving.
31
+ s.dependency "AdvergicSDK", ">= 0.1.4"
32
+ end
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Advergic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,329 @@
1
+ # Getting Started with Advergic React Native SDK
2
+
3
+ Welcome! This guide will help you integrate mobile advertising into your React Native app in just a few minutes.
4
+
5
+ ## 📋 Prerequisites
6
+
7
+ - **React Native**: 0.85.3 or higher
8
+ - **Node.js**: 18.0 or higher
9
+ - **Android**: API 24+ (Android 7.0) for Android apps
10
+ - **iOS**: 15.0+ for iOS apps
11
+ - **API Key**: Get one from [app.advergic.com](https://app.advergic.com)
12
+
13
+ ## 🚀 5-Minute Setup
14
+
15
+ ### Step 1: Install the SDK
16
+
17
+ ```bash
18
+ npm install @advergic-ads/react-native-sdk
19
+ ```
20
+
21
+ or with Yarn:
22
+
23
+ ```bash
24
+ yarn add @advergic-ads/react-native-sdk
25
+ ```
26
+
27
+ ### Step 2: Android Setup (One Line!)
28
+
29
+ Add your Google Ads Application ID to `android/app/src/main/AndroidManifest.xml`:
30
+
31
+ ```xml
32
+ <application>
33
+ <meta-data
34
+ android:name="com.google.android.gms.ads.APPLICATION_ID"
35
+ android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy" />
36
+ </application>
37
+ ```
38
+
39
+ Get your ID from [AdMob Console](https://admob.google.com).
40
+
41
+ **That's it!** React Native auto-linking handles the rest.
42
+
43
+ ### Step 3: iOS Setup
44
+
45
+ The React Native module is auto-linked, but the native `AdvergicSDK` is **not**
46
+ published on the CocoaPods trunk — it ships from Advergic's own podspec. Add
47
+ that one line to your `Podfile` (alongside the iOS 15.0 deployment target),
48
+ then install:
49
+
50
+ ```ruby
51
+ # ios/Podfile
52
+ platform :ios, '15.0'
53
+
54
+ target 'YourApp' do
55
+ # ...existing config (use_react_native! etc.)...
56
+
57
+ # Required: makes the native Advergic iOS SDK resolvable by CocoaPods.
58
+ pod 'AdvergicSDK', :podspec => 'https://app.advergic.com/ios/sdk/AdvergicSDK.podspec'
59
+ end
60
+ ```
61
+
62
+ ```bash
63
+ cd ios && pod install && cd ..
64
+ ```
65
+
66
+ Once that line is present, CocoaPods resolves `AdvergicReactNative` (auto-linked)
67
+ and its `AdvergicSDK` dependency, and pulls in Google Mobile Ads automatically.
68
+ Prebid is bundled inside the `AdvergicSDK` framework.
69
+
70
+ > **Why the extra line?** CocoaPods can only resolve a pod's dependencies from
71
+ > the trunk or from sources declared in *your* `Podfile`. Since `AdvergicSDK` is
72
+ > distributed via a hosted podspec rather than the trunk, the consuming app must
73
+ > point CocoaPods at it. The hosted URL always serves the latest SDK version.
74
+
75
+ > **Note:** iOS supports banner, interstitial, rewarded, and app-open ads.
76
+ > Native ads are currently Android-only.
77
+
78
+ ### Step 4: Initialize in Your App
79
+
80
+ ```tsx
81
+ import React, { useEffect } from "react";
82
+ import Advergic from "@advergic-ads/react-native-sdk";
83
+
84
+ export default function App() {
85
+ useEffect(() => {
86
+ // Initialize the SDK on app launch
87
+ Advergic.initialize("YOUR_API_KEY").then((initialized) => {
88
+ if (initialized) {
89
+ console.log("✓ Advergic SDK ready!");
90
+ }
91
+ });
92
+ }, []);
93
+
94
+ return (
95
+ // Your app content
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### Step 5: Add Your First Banner Ad
101
+
102
+ ```tsx
103
+ import { AdvergicBannerAd } from "@advergic-ads/react-native-sdk";
104
+
105
+ export default function Home() {
106
+ return (
107
+ <View>
108
+ {/* Your content */}
109
+
110
+ <AdvergicBannerAd
111
+ adUnitId="/23315275002/home_banner_320x50_ad_unit_id"
112
+ style={{ width: 320, height: 50, alignSelf: "center" }}
113
+ onAdLoaded={() => console.log("Ad loaded!")}
114
+ onAdFailedToLoad={(error) => console.error("Ad failed:", error)}
115
+ />
116
+ </View>
117
+ );
118
+ }
119
+ ```
120
+
121
+ That's it! You now have banner ads running. 🎉
122
+
123
+ ---
124
+
125
+ ## 📦 Ad Formats
126
+
127
+ The SDK supports 4 ad formats:
128
+
129
+ ### 1. **Banner Ads** ✓ Easiest
130
+
131
+ Rectangular ads that display inline with your content.
132
+
133
+ ```tsx
134
+ <AdvergicBannerAd
135
+ adUnitId="/YOUR_NETWORK_ID/YOUR_BANNER_AD_UNIT"
136
+ style={{ width: 320, height: 50 }}
137
+ />
138
+ ```
139
+
140
+ **Common sizes:**
141
+ - `320x50` — Standard mobile banner
142
+ - `300x250` — Medium rectangle
143
+ - `320x100` — Large banner
144
+ - `300x600` — Half page
145
+
146
+ ### 2. **Interstitial Ads** — Full Screen
147
+
148
+ Full-screen ads shown between content (e.g., between game levels, after article read).
149
+
150
+ ```tsx
151
+ import Advergic from "@advergic-ads/react-native-sdk";
152
+
153
+ // Show when appropriate
154
+ Advergic.showInterstitialAd("/YOUR_NETWORK_ID/YOUR_INTERSTITIAL_AD_UNIT");
155
+
156
+ // Listen for close
157
+ Advergic.onInterstitialAdClosed(() => {
158
+ console.log("User closed the ad");
159
+ });
160
+ ```
161
+
162
+ ### 3. **Rewarded Ads** — Earn Points
163
+
164
+ Video ads that reward users for watching (unlock premium, earn coins, etc.).
165
+
166
+ ```tsx
167
+ // Show when user wants premium content
168
+ Advergic.showRewardedAd("/YOUR_NETWORK_ID/YOUR_REWARDED_AD_UNIT");
169
+
170
+ // Listen for reward grant
171
+ Advergic.onRewardedAdUserEarnedReward(() => {
172
+ unlockPremiumContent();
173
+ });
174
+ ```
175
+
176
+ ### 4. **Native Ads** — Custom Design
177
+
178
+ Get ad data and render it your own way.
179
+
180
+ ```tsx
181
+ const nativeAd = await Advergic.loadNativeAd("/YOUR_NETWORK_ID/YOUR_NATIVE_AD_UNIT");
182
+
183
+ return (
184
+ <View>
185
+ <Image source={{ uri: nativeAd.mainImage }} />
186
+ <Text>{nativeAd.title}</Text>
187
+ <Button title={nativeAd.callToAction} />
188
+ </View>
189
+ );
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 📱 Your First Complete App
195
+
196
+ Here's a minimal app with both banner and interstitial ads:
197
+
198
+ ```tsx
199
+ import React, { useEffect, useState } from "react";
200
+ import { View, Text, Button, StyleSheet, ScrollView } from "react-native";
201
+ import Advergic, { AdvergicBannerAd } from "@advergic-ads/react-native-sdk";
202
+
203
+ const API_KEY = "YOUR_API_KEY";
204
+
205
+ export default function App() {
206
+ const [initialized, setInitialized] = useState(false);
207
+
208
+ useEffect(() => {
209
+ // Initialize SDK
210
+ Advergic.initialize(API_KEY).then(setInitialized);
211
+
212
+ // Listen for interstitial close
213
+ const sub = Advergic.onInterstitialAdClosed(() => {
214
+ console.log("Interstitial closed");
215
+ });
216
+
217
+ return () => sub.remove();
218
+ }, []);
219
+
220
+ const showInterstitial = () => {
221
+ if (initialized) {
222
+ Advergic.showInterstitialAd(
223
+ "/23315275002/start_game_interstitial_ad_unit_id"
224
+ );
225
+ }
226
+ };
227
+
228
+ return (
229
+ <ScrollView style={styles.container}>
230
+ <Text style={styles.title}>My App</Text>
231
+
232
+ {/* Top banner ad */}
233
+ <AdvergicBannerAd
234
+ adUnitId="/23315275002/home_banner_320x50_ad_unit_id"
235
+ style={{ width: 320, height: 50, alignSelf: "center" }}
236
+ />
237
+
238
+ {/* App content */}
239
+ <Text style={styles.text}>Welcome to your app!</Text>
240
+
241
+ <Button
242
+ title="Show Interstitial Ad"
243
+ onPress={showInterstitial}
244
+ disabled={!initialized}
245
+ />
246
+
247
+ {/* Bottom banner ad */}
248
+ <AdvergicBannerAd
249
+ adUnitId="/23315275002/home_screen_top_banner_video_300x250_ad_unit_id"
250
+ style={{ width: 300, height: 250, alignSelf: "center", marginVertical: 20 }}
251
+ />
252
+ </ScrollView>
253
+ );
254
+ }
255
+
256
+ const styles = StyleSheet.create({
257
+ container: { flex: 1, padding: 20, backgroundColor: "#f5f5f5" },
258
+ title: { fontSize: 28, fontWeight: "bold", marginBottom: 20 },
259
+ text: { fontSize: 16, marginBottom: 20, color: "#333" },
260
+ });
261
+ ```
262
+
263
+ ---
264
+
265
+ ## 🔑 Getting Your API Key & Ad Unit IDs
266
+
267
+ 1. Sign up at [app.advergic.com](https://app.advergic.com)
268
+ 2. Create a new app
269
+ 3. Copy your **API Key** (use in `Advergic.initialize()`)
270
+ 4. Go to **Inventory → Ad Units** to create ad units
271
+ 5. Copy the **Ad Unit IDs** and use them in your components
272
+
273
+ ---
274
+
275
+ ## 🐛 Troubleshooting
276
+
277
+ ### "Banner is blank"
278
+ - Ensure `Advergic.initialize()` is called before rendering banners
279
+ - Check that the app has internet access
280
+ - Verify the ad unit ID is correct
281
+ - Add `onAdFailedToLoad` callback to see error details
282
+
283
+ ### "Interstitial doesn't show"
284
+ - Make sure you're using the correct ad unit ID (should be an interstitial, not banner)
285
+ - Call `showInterstitialAd()` after the app is initialized
286
+ - Some devices may not have ads available; add error handling
287
+
288
+ ### "Module not found" on Android
289
+ - Run `npm install` again
290
+ - Try `npm start -- --reset-cache` to clear React Native cache
291
+ - Delete `node_modules` and reinstall if still broken
292
+
293
+ ### Debug Mode
294
+
295
+ Enable verbose logging in Android logcat:
296
+
297
+ ```bash
298
+ adb logcat | grep -E "Advergic|Banner"
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 📚 Next Steps
304
+
305
+ - **Read the full docs**: [README.md](./README.md)
306
+ - **Check examples**: [examples/dramafy/](./examples/dramafy/)
307
+ - **API Reference**: [README.md → API Reference](./README.md#api-reference)
308
+ - **Troubleshooting**: [README.md → Troubleshooting](./README.md#troubleshooting)
309
+
310
+ ---
311
+
312
+ ## 💡 Tips
313
+
314
+ ✅ **Do:**
315
+ - Use fixed pixel dimensions for banner size (not percentages)
316
+ - Initialize SDK early in your app lifecycle
317
+ - Test with your own app before going live
318
+ - Monitor fill rates in the dashboard
319
+
320
+ ❌ **Don't:**
321
+ - Change ad unit IDs frequently (disrupts analytics)
322
+ - Show multiple ads simultaneously in the same location
323
+ - Initialize the SDK multiple times
324
+
325
+ ---
326
+
327
+ ## 🤝 Need Help?
328
+
329
+ - **Email**: hello@advergic.com
@@ -0,0 +1,58 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'org.jetbrains.kotlin.android'
4
+ }
5
+
6
+ android {
7
+ namespace 'com.advergic.reactnative'
8
+ compileSdk 35
9
+
10
+ defaultConfig {
11
+ minSdk 24
12
+ targetSdk 35
13
+ }
14
+
15
+ compileOptions {
16
+ sourceCompatibility JavaVersion.VERSION_17
17
+ targetCompatibility JavaVersion.VERSION_17
18
+ }
19
+ }
20
+
21
+ repositories {
22
+ flatDir {
23
+ // Try multiple locations for the AAR
24
+ dirs file("${projectDir}/libs")
25
+ dirs file("${projectDir.absolutePath}/../libs")
26
+ dirs file("${rootDir}/app/libs")
27
+ dirs file(rootDir).parentFile.absolutePath + "/node_modules/@advergic/advergic-react-native/android/libs"
28
+ }
29
+ }
30
+
31
+ dependencies {
32
+ api(name: 'advergic-sdk', ext: 'aar')
33
+
34
+ // React Native
35
+ compileOnly 'com.facebook.react:react-android'
36
+ compileOnly 'com.facebook.react:react-native'
37
+
38
+ // AndroidX and Lifecycle
39
+ api 'androidx.lifecycle:lifecycle-livedata-ktx:2.9.0'
40
+ api 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.0'
41
+ api 'androidx.room:room-runtime:2.7.0'
42
+ api 'androidx.room:room-ktx:2.7.0'
43
+
44
+ // Google Play Services and Material
45
+ api 'com.google.android.material:material:1.9.0'
46
+ api 'com.google.code.gson:gson:2.10.1'
47
+ api 'com.google.android.gms:play-services-ads:24.6.0'
48
+ api 'com.google.android.gms:play-services-ads-identifier:18.2.0'
49
+
50
+ // Third-party libraries
51
+ api 'com.applovin:applovin-sdk:13.2.0'
52
+ api 'com.squareup.retrofit2:retrofit:2.9.0'
53
+ api 'com.squareup.retrofit2:converter-gson:2.9.0'
54
+ api 'com.squareup.okhttp3:okhttp:4.11.0'
55
+ api 'com.github.bumptech.glide:glide:4.15.1'
56
+ api 'io.opentelemetry:opentelemetry-api:1.42.1'
57
+ api 'androidx.multidex:multidex:2.0.1'
58
+ }
@@ -0,0 +1,194 @@
1
+ package com.advergic.reactnative
2
+
3
+ import android.app.Activity
4
+ import android.util.Log
5
+ import android.view.View
6
+ import android.view.ViewTreeObserver
7
+ import android.widget.FrameLayout
8
+ import com.advergic.sdk.core.AdvergicSdk
9
+ import com.advergic.sdk.interfaces.BannerAdCallback
10
+ import com.facebook.react.bridge.Arguments
11
+ import com.facebook.react.bridge.ReadableArray
12
+ import com.facebook.react.bridge.WritableMap
13
+ import com.facebook.react.uimanager.SimpleViewManager
14
+ import com.facebook.react.uimanager.ThemedReactContext
15
+ import com.facebook.react.uimanager.UIManagerHelper
16
+ import com.facebook.react.uimanager.annotations.ReactProp
17
+ import com.facebook.react.uimanager.events.Event
18
+ import com.facebook.react.uimanager.events.RCTEventEmitter
19
+
20
+ private const val TAG = "AdvergicBanner"
21
+
22
+ class AdvergicBannerViewManager : SimpleViewManager<FrameLayout>() {
23
+
24
+ companion object {
25
+ const val REACT_CLASS = "AdvergicBannerView"
26
+ private const val COMMAND_LOAD_AD = "loadAd"
27
+ }
28
+
29
+ override fun getName(): String = REACT_CLASS
30
+
31
+ override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
32
+ return AdvergicBannerContainerView(reactContext)
33
+ }
34
+
35
+ @ReactProp(name = "adUnitId")
36
+ fun setAdUnitId(view: FrameLayout, adUnitId: String?) {
37
+ if (view is AdvergicBannerContainerView && adUnitId != null) {
38
+ view.setAdUnitId(adUnitId)
39
+ }
40
+ }
41
+
42
+ override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any>? {
43
+ return mapOf(
44
+ "onAdLoaded" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onAdLoaded")),
45
+ "onAdFailedToLoad" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onAdFailedToLoad")),
46
+ "onAdClicked" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onAdClicked")),
47
+ "onAdImpression" to mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onAdImpression"))
48
+ )
49
+ }
50
+
51
+ override fun receiveCommand(view: FrameLayout, commandId: String, args: ReadableArray?) {
52
+ if (COMMAND_LOAD_AD == commandId && view is AdvergicBannerContainerView) {
53
+ view.loadAd()
54
+ }
55
+ }
56
+
57
+ class AdvergicBannerContainerView(private val reactContext: ThemedReactContext) : FrameLayout(reactContext) {
58
+
59
+ private var adUnitId: String? = null
60
+ private var adLoaded = false
61
+ private var hasLoadedAd = false
62
+ private var loadRetries = 0
63
+ private val maxLoadRetries = 10
64
+
65
+ fun setAdUnitId(id: String) {
66
+ adUnitId = id
67
+ if (!hasLoadedAd) {
68
+ loadAd()
69
+ }
70
+ }
71
+
72
+ fun loadAd() {
73
+ val activity = reactContext.currentActivity
74
+ Log.d(TAG, "loadAd called - activity=$activity, adUnitId=$adUnitId, viewId=$id")
75
+
76
+ if (activity == null || adUnitId == null) {
77
+ Log.e(TAG, "Cannot load ad: activity=$activity, adUnitId=$adUnitId")
78
+ return
79
+ }
80
+
81
+ hasLoadedAd = true
82
+ activity.runOnUiThread {
83
+ loadAdWithRetry(activity)
84
+ }
85
+ }
86
+
87
+ private fun loadAdWithRetry(activity: Activity) {
88
+ try {
89
+ Log.d(TAG, "Calling showBannerAd for $adUnitId (attempt ${loadRetries + 1})")
90
+ AdvergicSdk.showBannerAd(activity, adUnitId!!, this, object : BannerAdCallback {
91
+ override fun onAdLoaded() {
92
+ Log.d(TAG, "Banner ad loaded: $adUnitId")
93
+ adLoaded = true
94
+ post { forceLayoutPass() }
95
+ postDelayed({
96
+ applyCreativeSizeFixOnAllChildren()
97
+ forceLayoutPass()
98
+ sendEvent("onAdLoaded", null)
99
+ }, 100)
100
+ }
101
+
102
+ override fun onAdFailedToLoad(e: String) {
103
+ Log.e(TAG, "Banner ad failed: $adUnitId - $e")
104
+ if (e.contains("not initialized", ignoreCase = true) && loadRetries < maxLoadRetries) {
105
+ loadRetries++
106
+ postDelayed({ loadAdWithRetry(activity) }, 300L * loadRetries)
107
+ Log.d(TAG, "Retrying ad load in ${300L * loadRetries}ms")
108
+ } else {
109
+ sendEvent("onAdFailedToLoad", createErrorMap(e))
110
+ }
111
+ }
112
+
113
+ override fun onAdClicked() {
114
+ Log.d(TAG, "Banner ad clicked: $adUnitId")
115
+ sendEvent("onAdClicked", null)
116
+ }
117
+
118
+ override fun onAdImpression() {
119
+ Log.d(TAG, "Banner ad impression: $adUnitId")
120
+ sendEvent("onAdImpression", null)
121
+ }
122
+ })
123
+ } catch (e: Exception) {
124
+ Log.e(TAG, "Exception calling showBannerAd: ${e.message}", e)
125
+ sendEvent("onAdFailedToLoad", createErrorMap(e.message ?: "Unknown error"))
126
+ }
127
+ }
128
+
129
+ private fun createErrorMap(error: String): WritableMap {
130
+ val map = Arguments.createMap()
131
+ map.putString("error", error)
132
+ return map
133
+ }
134
+
135
+ private inner class BannerAdEvent(
136
+ val eventNameValue: String,
137
+ val params: WritableMap?
138
+ ) : Event<BannerAdEvent>(id) {
139
+ override fun getEventName() = eventNameValue
140
+ override fun dispatch(rctEventEmitter: RCTEventEmitter) {
141
+ rctEventEmitter.receiveEvent(id, eventNameValue, params)
142
+ }
143
+ }
144
+
145
+ private fun sendEvent(eventName: String, params: WritableMap?) {
146
+ val eventDispatcher = UIManagerHelper.getEventDispatcher(reactContext)
147
+ if (eventDispatcher != null) {
148
+ eventDispatcher.dispatchEvent(BannerAdEvent(eventName, params))
149
+ }
150
+ }
151
+
152
+ private fun dpToPx(dp: Int): Int {
153
+ return (dp * resources.displayMetrics.density).toInt()
154
+ }
155
+
156
+ private fun forceLayoutPass() {
157
+ val w = width
158
+ val h = height
159
+ if (w <= 0 || h <= 0) return
160
+ measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
161
+ MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY))
162
+ layout(0, 0, w, h)
163
+ }
164
+
165
+ private fun applyCreativeSizeFixOnAllChildren() {
166
+ for (i in 0 until childCount) {
167
+ applyCreativeSizeFix(getChildAt(i), 0)
168
+ }
169
+ }
170
+
171
+ private fun applyCreativeSizeFix(view: View?, attempt: Int) {
172
+ if (view == null) return
173
+ if (attempt > 10) return
174
+
175
+ val w = view.width
176
+ val h = view.height
177
+
178
+ if (w > 0 && h > dpToPx(10)) {
179
+ measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
180
+ MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY))
181
+ layout(0, 0, w, h)
182
+ return
183
+ }
184
+
185
+ view.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
186
+ override fun onPreDraw(): Boolean {
187
+ view.viewTreeObserver.removeOnPreDrawListener(this)
188
+ applyCreativeSizeFix(view, attempt + 1)
189
+ return true
190
+ }
191
+ })
192
+ }
193
+ }
194
+ }