cloudx-react-native 3.1.2 → 3.2.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.
package/README.md CHANGED
@@ -19,6 +19,7 @@ The CloudX React Native SDK enables monetization of your React Native apps with
19
19
 
20
20
  | Plugin | iOS (`CloudXCore`) | Android (`io.cloudx:sdk`) |
21
21
  |--------|-------------------|---------------------------|
22
+ | 3.2.0 | 3.2.0 | 3.2.0 |
22
23
  | 3.1.2 | 3.1.0 | 3.1.0 |
23
24
  | 2.2.3 | 2.2.3 | 2.2.3 |
24
25
 
@@ -39,12 +40,13 @@ target 'YourApp' do
39
40
  # ... existing config ...
40
41
 
41
42
  # CloudX ad network adapters (add as needed)
42
- pod 'CloudXMetaAdapter', '~> 3.1.0'
43
- pod 'CloudXVungleAdapter', '~> 3.1.0'
44
- pod 'CloudXInMobiAdapter', '~> 3.1.0'
45
- pod 'CloudXMintegralAdapter', '~> 3.1.0'
46
- pod 'CloudXUnityAdsAdapter', '~> 3.1.0'
47
- pod 'CloudXRenderer', '~> 3.1.0'
43
+ pod 'CloudXMetaAdapter', '~> 3.2.0'
44
+ pod 'CloudXVungleAdapter', '~> 3.2.0'
45
+ pod 'CloudXInMobiAdapter', '~> 3.2.0'
46
+ pod 'CloudXMintegralAdapter', '~> 3.2.0'
47
+ pod 'CloudXUnityAdsAdapter', '~> 3.2.0'
48
+ pod 'CloudXMagniteAdapter', '~> 3.2.0'
49
+ pod 'CloudXRenderer', '~> 3.2.0'
48
50
  end
49
51
  ```
50
52
 
@@ -75,16 +77,17 @@ Add CloudX SDK and adapter dependencies to your `android/app/build.gradle`:
75
77
  ```kotlin
76
78
  dependencies {
77
79
  // CloudX Android SDK
78
- implementation "io.cloudx:sdk:3.1.0"
80
+ implementation "io.cloudx:sdk:3.2.0"
79
81
 
80
82
  // Adapters for ad networks (add as needed)
81
- implementation "io.cloudx:adapter-meta:3.1.0"
82
- implementation "io.cloudx:adapter-vungle:3.1.0"
83
- implementation "io.cloudx:adapter-inmobi:3.1.0"
84
- implementation "io.cloudx:adapter-mintegral:3.1.0"
85
- implementation "io.cloudx:adapter-unityads:3.1.0"
86
-
87
- // Required by the native SDK 3.1.0 when using the Meta adapter.
83
+ implementation "io.cloudx:adapter-meta:3.2.0"
84
+ implementation "io.cloudx:adapter-vungle:3.2.0"
85
+ implementation "io.cloudx:adapter-inmobi:3.2.0"
86
+ implementation "io.cloudx:adapter-mintegral:3.2.0"
87
+ implementation "io.cloudx:adapter-unityads:3.2.0"
88
+ implementation "io.cloudx:adapter-magnite:3.2.0"
89
+
90
+ // Required by the native SDK 3.2.0 when using the Meta adapter.
88
91
  implementation "com.facebook.android:audience-network-sdk:6.21.0"
89
92
  }
90
93
  ```
@@ -637,12 +640,12 @@ CloudX.setVisualDebuggingEnabled(true);
637
640
  - Xcode 14.0+ / CocoaPods (iOS)
638
641
  - Gradle 8.0+ / Kotlin 1.8+ (Android)
639
642
 
640
- ### Native SDK Versions (3.1.0)
643
+ ### Native SDK Versions (3.2.0)
641
644
 
642
645
  | Platform | Dependency | Version |
643
646
  |----------|-----------|---------|
644
- | iOS | CloudXCore | ~> 3.1.0 |
645
- | Android | io.cloudx:sdk | 3.1.0 |
647
+ | iOS | CloudXCore | ~> 3.2.0 |
648
+ | Android | io.cloudx:sdk | 3.2.0 |
646
649
 
647
650
  ## Troubleshooting
648
651
 
@@ -65,7 +65,6 @@ android {
65
65
  }
66
66
 
67
67
  repositories {
68
- mavenLocal()
69
68
  mavenCentral()
70
69
  google()
71
70
  }
@@ -73,5 +72,5 @@ repositories {
73
72
  dependencies {
74
73
  implementation "com.facebook.react:react-native:0.76.2"
75
74
 
76
- implementation "io.cloudx:sdk:3.1.0"
75
+ implementation "io.cloudx:sdk:3.2.0"
77
76
  }
@@ -208,20 +208,24 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
208
208
  }
209
209
 
210
210
  fun showInterstitial(adUnitId: String, placement: String?, customData: String?) {
211
- val interstitial = retrieveInterstitial(adUnitId)
212
- val activity = maybeGetCurrentActivity()
213
- if (activity == null) {
214
- Log.e(TAG, "showInterstitial: no activity available")
215
- return
211
+ reactApplicationContext.runOnUiQueueThread {
212
+ val interstitial = retrieveInterstitial(adUnitId)
213
+ val activity = maybeGetCurrentActivity()
214
+ if (activity == null) {
215
+ Log.e(TAG, "showInterstitial: no activity available")
216
+ return@runOnUiQueueThread
217
+ }
218
+ interstitial.show(activity, placement, customData)
216
219
  }
217
- interstitial.show(activity, placement, customData)
218
220
  }
219
221
 
220
222
  fun destroyInterstitial(adUnitId: String) {
221
- interstitials.remove(adUnitId)?.let {
222
- it.listener = null
223
- it.revenueListener = null
224
- it.destroy()
223
+ reactApplicationContext.runOnUiQueueThread {
224
+ interstitials.remove(adUnitId)?.let {
225
+ it.listener = null
226
+ it.revenueListener = null
227
+ it.destroy()
228
+ }
225
229
  }
226
230
  }
227
231
 
@@ -238,20 +242,24 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
238
242
  }
239
243
 
240
244
  fun showRewarded(adUnitId: String, placement: String?, customData: String?) {
241
- val rewarded = retrieveRewarded(adUnitId)
242
- val activity = maybeGetCurrentActivity()
243
- if (activity == null) {
244
- Log.e(TAG, "showRewarded: no activity available")
245
- return
245
+ reactApplicationContext.runOnUiQueueThread {
246
+ val rewarded = retrieveRewarded(adUnitId)
247
+ val activity = maybeGetCurrentActivity()
248
+ if (activity == null) {
249
+ Log.e(TAG, "showRewarded: no activity available")
250
+ return@runOnUiQueueThread
251
+ }
252
+ rewarded.show(activity, placement, customData)
246
253
  }
247
- rewarded.show(activity, placement, customData)
248
254
  }
249
255
 
250
256
  fun destroyRewarded(adUnitId: String) {
251
- rewardedAds.remove(adUnitId)?.let {
252
- it.listener = null
253
- it.revenueListener = null
254
- it.destroy()
257
+ reactApplicationContext.runOnUiQueueThread {
258
+ rewardedAds.remove(adUnitId)?.let {
259
+ it.listener = null
260
+ it.revenueListener = null
261
+ it.destroy()
262
+ }
255
263
  }
256
264
  }
257
265
 
@@ -661,15 +669,17 @@ class CloudXModuleImpl(private val reactApplicationContext: ReactApplicationCont
661
669
  destroyAdView(adUnitId, adFormat)
662
670
  }
663
671
 
664
- for (interstitial in interstitials.values) {
665
- interstitial.destroy()
666
- }
667
- interstitials.clear()
672
+ reactApplicationContext.runOnUiQueueThread {
673
+ for (interstitial in interstitials.values) {
674
+ interstitial.destroy()
675
+ }
676
+ interstitials.clear()
668
677
 
669
- for (rewarded in rewardedAds.values) {
670
- rewarded.destroy()
678
+ for (rewarded in rewardedAds.values) {
679
+ rewarded.destroy()
680
+ }
681
+ rewardedAds.clear()
671
682
  }
672
- rewardedAds.clear()
673
683
  }
674
684
 
675
685
  // =========================================================================
@@ -1,5 +1,6 @@
1
1
  package com.cloudxreactnative
2
2
 
3
+ import com.facebook.react.bridge.Dynamic
3
4
  import com.facebook.react.bridge.Promise
4
5
  import com.facebook.react.bridge.ReactApplicationContext
5
6
  import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -25,8 +26,23 @@ class CloudXModule(reactContext: ReactApplicationContext) :
25
26
  @ReactMethod fun setAppKeyValue(key: String, value: String) = impl.setAppKeyValue(key, value)
26
27
  @ReactMethod fun clearAllKeyValues() = impl.clearAllKeyValues()
27
28
 
28
- @ReactMethod fun setHasUserConsent(hasUserConsent: Boolean?) = impl.setHasUserConsent(hasUserConsent)
29
- @ReactMethod fun setDoNotSell(isDoNotSell: Boolean?) = impl.setDoNotSell(isDoNotSell)
29
+ // Paper's `JavaMethodWrapper` extracts primitive boxed types via
30
+ // `ReadableNativeArray.getBoolean(int)` which unconditionally calls
31
+ // `booleanValue()` on the boxed result — null arguments therefore throw
32
+ // `NativeArgumentsParseException` *before* the method runs, even when the
33
+ // Kotlin signature is `Boolean?`. Declaring the parameter as `Dynamic`
34
+ // lets the bridge hand us the raw value and we null-check it ourselves.
35
+ // TurboModules (NewArch) don't have this limitation, which is why the
36
+ // newarch counterpart keeps the `Boolean?` signature directly.
37
+ @ReactMethod fun setHasUserConsent(hasUserConsent: Dynamic) {
38
+ val value: Boolean? = if (hasUserConsent.isNull) null else hasUserConsent.asBoolean()
39
+ impl.setHasUserConsent(value)
40
+ }
41
+
42
+ @ReactMethod fun setDoNotSell(isDoNotSell: Dynamic) {
43
+ val value: Boolean? = if (isDoNotSell.isNull) null else isDoNotSell.asBoolean()
44
+ impl.setDoNotSell(value)
45
+ }
30
46
 
31
47
  @ReactMethod fun createBanner(adUnitId: String, position: String) = impl.createBanner(adUnitId, position)
32
48
  @ReactMethod fun createBannerWithOffsets(adUnitId: String, position: String, xOffset: Double, yOffset: Double) = impl.createBannerWithOffsets(adUnitId, position, xOffset, yOffset)
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
14
14
 
15
15
  s.requires_arc = true
16
16
 
17
- s.dependency "CloudXCore", "~> 3.1.0"
17
+ s.dependency "CloudXCore", "~> 3.2.0"
18
18
 
19
19
  s.source_files = "ios/**/*.{h,mm}"
20
20
 
package/ios/CloudX.mm CHANGED
@@ -11,6 +11,42 @@
11
11
  + (nullable UIColor *)clx_colorWithHexString:(NSString *)hexString;
12
12
  @end
13
13
 
14
+ // Diagnostic-only observer. Inserted as a zero-size hidden subview of the
15
+ // CLXBannerAdView so we can detect when the banner's window attachment changes.
16
+ // A subview's window follows its superview's, so window transitions on the
17
+ // observer are a reliable proxy for transitions on the banner itself.
18
+ // TODO(CXD-banner-diag): remove once the iOS RN banner visibility issue is
19
+ // root-caused and fixed.
20
+ @interface CLXDiagBannerWindowObserver : UIView
21
+ @property (nonatomic, copy, nullable) NSString *adUnitIdentifier;
22
+ @end
23
+
24
+ @implementation CLXDiagBannerWindowObserver
25
+
26
+ - (instancetype)initWithAdUnitIdentifier:(NSString *)adUnitIdentifier
27
+ {
28
+ self = [super initWithFrame: CGRectZero];
29
+ if (self) {
30
+ _adUnitIdentifier = [adUnitIdentifier copy];
31
+ self.hidden = YES;
32
+ self.userInteractionEnabled = NO;
33
+ self.translatesAutoresizingMaskIntoConstraints = YES;
34
+ }
35
+ return self;
36
+ }
37
+
38
+ - (void)didMoveToWindow
39
+ {
40
+ [super didMoveToWindow];
41
+ NSLog(@"[CloudXCore] [CloudX] DIAG banner[%@] didMoveToWindow: window=%@ superview=%@ superview.window=%@",
42
+ self.adUnitIdentifier ?: @"(nil)",
43
+ self.window,
44
+ self.superview ? NSStringFromClass([self.superview class]) : @"(nil)",
45
+ self.superview ? self.superview.window : @"(nil)");
46
+ }
47
+
48
+ @end
49
+
14
50
  @interface CloudX()
15
51
 
16
52
  // A simple BOOL tracks initialization state. Concurrent initialize calls are serialized
@@ -840,14 +876,22 @@ RCT_EXPORT_METHOD(destroyRewarded:(NSString *)adUnitIdentifier)
840
876
  // on window attachment" path inserts the adapter view immediately.
841
877
  if ( !view.superview )
842
878
  {
879
+ // TODO(CXD-banner-diag): remove once the iOS RN banner visibility
880
+ // issue is root-caused and fixed.
881
+ [self logBannerAttachDiagnosticsForAdUnit: adUnitIdentifier phase: @"pre-attach" view: view];
882
+
843
883
  UIView *rootView = [self topViewController].view;
844
884
  if ( rootView )
845
885
  {
846
886
  [rootView addSubview: view];
887
+ [self attachDiagObserverToBanner: view adUnit: adUnitIdentifier];
888
+ [self logBannerAttachDiagnosticsForAdUnit: adUnitIdentifier phase: @"post-attach" view: view];
889
+ [self scheduleBannerAttachFollowupsForAdUnit: adUnitIdentifier view: view];
847
890
  }
848
891
  else
849
892
  {
850
893
  [self log: @"showAdView: no visible root view for ad unit \"%@\"; deferring.", adUnitIdentifier];
894
+ [self logBannerAttachDiagnosticsForAdUnit: adUnitIdentifier phase: @"no-root-view" view: view];
851
895
  [self.adUnitIdentifiersToShowAfterCreate addObject: adUnitIdentifier];
852
896
  return;
853
897
  }
@@ -862,6 +906,68 @@ RCT_EXPORT_METHOD(destroyRewarded:(NSString *)adUnitIdentifier)
862
906
  }];
863
907
  }
864
908
 
909
+ // TODO(CXD-banner-diag): remove once the iOS RN banner visibility issue is
910
+ // root-caused and fixed.
911
+ - (void)logBannerAttachDiagnosticsForAdUnit:(NSString *)adUnitIdentifier
912
+ phase:(NSString *)phase
913
+ view:(CLXBannerAdView *)view
914
+ {
915
+ UIViewController *topVC = [self topViewController];
916
+ UIViewController *rootVC = [self keyWindowRootViewController];
917
+ NSUInteger connectedSceneCount = UIApplication.sharedApplication.connectedScenes.count;
918
+ NSUInteger foregroundSceneCount = 0;
919
+ for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
920
+ if (scene.activationState == UISceneActivationStateForegroundActive) foregroundSceneCount++;
921
+ }
922
+
923
+ NSLog(@"[CloudXCore] [CloudX] DIAG banner[%@] %@: "
924
+ @"view.window=%@ view.superview=%@ view.hidden=%d "
925
+ @"topVC=%@ topVC.view.window=%@ "
926
+ @"rootVC=%@ rootVC.view.window=%@ "
927
+ @"scenes=total:%lu foregroundActive:%lu",
928
+ adUnitIdentifier,
929
+ phase,
930
+ view.window,
931
+ view.superview ? NSStringFromClass([view.superview class]) : @"(nil)",
932
+ view.hidden,
933
+ topVC ? NSStringFromClass([topVC class]) : @"(nil)",
934
+ topVC.view.window,
935
+ rootVC ? NSStringFromClass([rootVC class]) : @"(nil)",
936
+ rootVC.view.window,
937
+ (unsigned long)connectedSceneCount,
938
+ (unsigned long)foregroundSceneCount);
939
+ }
940
+
941
+ // TODO(CXD-banner-diag): remove once root-caused.
942
+ - (void)scheduleBannerAttachFollowupsForAdUnit:(NSString *)adUnitIdentifier
943
+ view:(CLXBannerAdView *)view
944
+ {
945
+ __weak CloudX *weakSelf = self;
946
+ __weak CLXBannerAdView *weakView = view;
947
+ NSArray<NSNumber *> *delaysSeconds = @[ @0.25, @1.0, @3.0, @10.0 ];
948
+ for (NSNumber *delay in delaysSeconds) {
949
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay.doubleValue * NSEC_PER_SEC)),
950
+ dispatch_get_main_queue(), ^{
951
+ CloudX *strongSelf = weakSelf;
952
+ CLXBannerAdView *strongView = weakView;
953
+ if (!strongSelf || !strongView) return;
954
+ NSString *phase = [NSString stringWithFormat: @"post-attach+%@s", delay];
955
+ [strongSelf logBannerAttachDiagnosticsForAdUnit: adUnitIdentifier phase: phase view: strongView];
956
+ });
957
+ }
958
+ }
959
+
960
+ // TODO(CXD-banner-diag): remove once root-caused.
961
+ - (void)attachDiagObserverToBanner:(CLXBannerAdView *)view
962
+ adUnit:(NSString *)adUnitIdentifier
963
+ {
964
+ for (UIView *subview in view.subviews) {
965
+ if ([subview isKindOfClass: [CLXDiagBannerWindowObserver class]]) return;
966
+ }
967
+ CLXDiagBannerWindowObserver *observer = [[CLXDiagBannerWindowObserver alloc] initWithAdUnitIdentifier: adUnitIdentifier];
968
+ [view addSubview: observer];
969
+ }
970
+
865
971
  - (void)hideAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(CLXBannerType)adFormat
866
972
  {
867
973
  [self executeOnMainQueue:^{
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.PLUGIN_VERSION = void 0;
7
7
  // Keep PLUGIN_VERSION in sync with the `version` field in package.json.
8
8
  // Phase 1 of RELEASE.md bumps both together.
9
- const PLUGIN_VERSION = exports.PLUGIN_VERSION = '3.1.2';
9
+ const PLUGIN_VERSION = exports.PLUGIN_VERSION = '3.2.0';
10
10
  //# sourceMappingURL=version.js.map
@@ -1,4 +1,4 @@
1
1
  // Keep PLUGIN_VERSION in sync with the `version` field in package.json.
2
2
  // Phase 1 of RELEASE.md bumps both together.
3
- export const PLUGIN_VERSION = '3.1.2';
3
+ export const PLUGIN_VERSION = '3.2.0';
4
4
  //# sourceMappingURL=version.js.map
@@ -1,2 +1,2 @@
1
- export declare const PLUGIN_VERSION = "3.1.2";
1
+ export declare const PLUGIN_VERSION = "3.2.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudx-react-native",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "description": "React Native wrapper for CloudX SDK (iOS & Android)",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Keep PLUGIN_VERSION in sync with the `version` field in package.json.
2
2
  // Phase 1 of RELEASE.md bumps both together.
3
- export const PLUGIN_VERSION = '3.1.2';
3
+ export const PLUGIN_VERSION = '3.2.0';