From d81c0872a398d4d1f23edc1ee6ff24b008bd63ab Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 13:09:43 -0300 Subject: [PATCH 01/16] feat: create ThreadSafe class --- ios/ThreadSafe.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 ios/ThreadSafe.swift diff --git a/ios/ThreadSafe.swift b/ios/ThreadSafe.swift new file mode 100644 index 000000000..8e7e14011 --- /dev/null +++ b/ios/ThreadSafe.swift @@ -0,0 +1,18 @@ +class ThreadSafe { + private var _value: A + private let queue = DispatchQueue(label: "ThreadSafe") + + init(_ value: A) { + self._value = value + } + + var value: A { + return queue.sync { _value } + } + + func atomically(_ transform: (inout A) -> Void) { + queue.sync { + transform(&_value) + } + } +} \ No newline at end of file From be187d3f135ce67f4a30af6a2d734077303b5699 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 13:10:23 -0300 Subject: [PATCH 02/16] feat: make validproducts var thread safe --- ios/RNIapIos.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ios/RNIapIos.swift b/ios/RNIapIos.swift index a18121f0a..07d3df166 100644 --- a/ios/RNIapIos.swift +++ b/ios/RNIapIos.swift @@ -1,5 +1,6 @@ import React import StoreKit +import ThreadSafe @objc(RNIapIos) class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate { @@ -8,7 +9,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver private var hasListeners = false private var pendingTransactionWithAutoFinish = false private var receiptBlock: ((Data?, Error?) -> Void)? // Block to handle request the receipt async from delegate - private var validProducts: [String: SKProduct] + private var validProducts: ThreadSafe<[String: SKProduct]> private var promotedPayment: SKPayment? private var promotedProduct: SKProduct? private var productsRequest: SKProductsRequest? @@ -19,7 +20,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver promisesByKey = [String: [RNIapIosPromise]]() pendingTransactionWithAutoFinish = false myQueue = DispatchQueue(label: "reject") - validProducts = [String: SKProduct]() + validProducts = ThreadSafe<[String: SKProduct]>() super.init() addTransactionObserver() } @@ -148,7 +149,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver stopObserving() rejectAllPendingPromises() receiptBlock = nil - validProducts.removeAll() + validProducts.atomically { $0.removeAll() } promotedPayment = nil promotedProduct = nil productsRequest = nil @@ -254,7 +255,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver reject: @escaping RCTPromiseRejectBlock = { _, _, _ in } ) { debugMessage("clear valid products") - validProducts.removeAll() + validProducts.atomically { $0.removeAll() } resolve(nil) } @@ -364,7 +365,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver // Doesn't allow duplication. Replace new product. func add(_ aProd: SKProduct) { debugMessage("Add new object: \(aProd.productIdentifier)") - validProducts[aProd.productIdentifier] = aProd + validProducts.atomically { $0[aProd.productIdentifier] = aProd } } func request(_ request: SKRequest, didFailWithError error: Error) { From 4da355728cdb23e6994e1cbab8abff8ed144d524 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 13:43:36 -0300 Subject: [PATCH 03/16] feat: create LatestPromiseKeeper --- ios/LatestPromiseKeeper.swift | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 ios/LatestPromiseKeeper.swift diff --git a/ios/LatestPromiseKeeper.swift b/ios/LatestPromiseKeeper.swift new file mode 100644 index 000000000..773ae9262 --- /dev/null +++ b/ios/LatestPromiseKeeper.swift @@ -0,0 +1,26 @@ +import ThreadSafe + +// Only keeps latest promise, assumes older promises are not needed +// Avoids racing conditions by storing latestPromise in a thread safe var +// Cancels previous promises when new ones are added +// Should not be used when all promises are relevant (e.g. Purchases) +class LatestPromiseKeeper { + private var latestPromise: ThreadSafe<(RCTPromiseResolveBlock, RCTPromiseRejectBlock)?> = ThreadSafe(nil) + private var latestRequest: ThreadSafe = ThreadSafe(nil) + + func setLatestPromise(request: SKProductsRequest, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + cancelOngoingRequest() + + latestRequest.atomically { $0 = request } + latestPromise.atomically { $0 = (resolve, reject) } + } + + func cancelOngoingRequest() { + latestRequest.atomically { ongoingRequest in + ongoingRequest?.cancel() + ongoingRequest = nil + } + + latestPromise.atomically { $0 = nil } + } +} \ No newline at end of file From aef6e7cc732c237af08f59bf394d5a7319da5640 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 13:44:36 -0300 Subject: [PATCH 04/16] feat: avoid conflicts with racing promises --- ios/RNIapIos.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ios/RNIapIos.swift b/ios/RNIapIos.swift index 07d3df166..dbed73e75 100644 --- a/ios/RNIapIos.swift +++ b/ios/RNIapIos.swift @@ -1,6 +1,7 @@ import React import StoreKit import ThreadSafe +import LatestPromiseKeeper @objc(RNIapIos) class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate { @@ -13,6 +14,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver private var promotedPayment: SKPayment? private var promotedProduct: SKProduct? private var productsRequest: SKProductsRequest? + private let latestPromiseKeeper = LatestPromiseKeeper() private var countPendingTransaction: Int = 0 private var hasTransactionObserver = false @@ -163,10 +165,13 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver ) { let productIdentifiers = Set(skus) productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers) + if let productsRequest = productsRequest { productsRequest.delegate = self - let key: String = productsRequest.key - addPromise(forKey: key, resolve: resolve, reject: reject) + + // Update latestPromiseKeeper with the new request and promise + self.latestPromiseKeeper.setLatestPromise(request: productsRequest, resolve: resolve, reject: reject) + productsRequest.start() } } @@ -349,6 +354,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver // StoreKitDelegate func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + // Add received products for prod in response.products { add(prod) } @@ -358,7 +364,13 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver items.append(getProductObject(product)) } - resolvePromises(forKey: request.key, value: items) + // Resolve latest promise with received items, if the finished request is the same as the latest one + self.latestPromiseKeeper.atomically { (latestPromiseResolvers, ongoingRequest) in + guard let (resolve, _) = latestPromiseResolvers, ongoingRequest == request else { + return + } + resolve(items) + } } // Add to valid products from Apple server response. Allowing getProducts, getSubscriptions call several times. From 40f45bec9c4d0db83f50a750660863d2af6acd91 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 14:28:51 -0300 Subject: [PATCH 05/16] fix: promise keeper import statements --- ios/LatestPromiseKeeper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/LatestPromiseKeeper.swift b/ios/LatestPromiseKeeper.swift index 773ae9262..6bf56d124 100644 --- a/ios/LatestPromiseKeeper.swift +++ b/ios/LatestPromiseKeeper.swift @@ -1,4 +1,4 @@ -import ThreadSafe +import StoreKit // Only keeps latest promise, assumes older promises are not needed // Avoids racing conditions by storing latestPromise in a thread safe var From b1e1513d2564931229e29fe9d450b8af81fa306a Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 14:29:49 -0300 Subject: [PATCH 06/16] fix: allow atomically value to be modified --- ios/ThreadSafe.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/ThreadSafe.swift b/ios/ThreadSafe.swift index 8e7e14011..cce277f89 100644 --- a/ios/ThreadSafe.swift +++ b/ios/ThreadSafe.swift @@ -1,18 +1,18 @@ class ThreadSafe { private var _value: A private let queue = DispatchQueue(label: "ThreadSafe") - + init(_ value: A) { self._value = value } - + var value: A { return queue.sync { _value } } - - func atomically(_ transform: (inout A) -> Void) { + + func atomically(_ transform: (inout A) -> ()) { queue.sync { - transform(&_value) + transform(&self._value) } } } \ No newline at end of file From 2bf2f91bb9fe68f12c3a6101f98850fe1d0acd99 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 14:30:10 -0300 Subject: [PATCH 07/16] fix: move resolve if matches logic to class --- ios/LatestPromiseKeeper.swift | 12 ++++++++++++ ios/RNIapIos.swift | 6 +----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ios/LatestPromiseKeeper.swift b/ios/LatestPromiseKeeper.swift index 6bf56d124..22ea16d9e 100644 --- a/ios/LatestPromiseKeeper.swift +++ b/ios/LatestPromiseKeeper.swift @@ -23,4 +23,16 @@ class LatestPromiseKeeper { latestPromise.atomically { $0 = nil } } + + func resolveIfRequestMatches(matchingRequest: SKProductsRequest, items: [[String: Any?]], operation: (RCTPromiseResolveBlock, [[String: Any?]]) -> Void) { + latestPromise.atomically { promiseResolvers in + guard let (resolve, _) = promiseResolvers else { return } + + latestRequest.atomically { ongoingRequest in + guard ongoingRequest === matchingRequest else { return } + + operation(resolve, items) + } + } + } } \ No newline at end of file diff --git a/ios/RNIapIos.swift b/ios/RNIapIos.swift index dbed73e75..7cefa3df7 100644 --- a/ios/RNIapIos.swift +++ b/ios/RNIapIos.swift @@ -364,11 +364,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver items.append(getProductObject(product)) } - // Resolve latest promise with received items, if the finished request is the same as the latest one - self.latestPromiseKeeper.atomically { (latestPromiseResolvers, ongoingRequest) in - guard let (resolve, _) = latestPromiseResolvers, ongoingRequest == request else { - return - } + self.latestPromiseKeeper.resolveIfRequestMatches(matchingRequest: request, items: items) { (resolve, items) in resolve(items) } } From 5da30a312c6321ba1eb1789b71b7151cf9130692 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Wed, 8 Nov 2023 14:30:36 -0300 Subject: [PATCH 08/16] fix: syntax when accessing validProducts dict --- ios/RNIapIos.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ios/RNIapIos.swift b/ios/RNIapIos.swift index 7cefa3df7..6915e650c 100644 --- a/ios/RNIapIos.swift +++ b/ios/RNIapIos.swift @@ -1,7 +1,5 @@ import React import StoreKit -import ThreadSafe -import LatestPromiseKeeper @objc(RNIapIos) class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate { @@ -22,7 +20,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver promisesByKey = [String: [RNIapIosPromise]]() pendingTransactionWithAutoFinish = false myQueue = DispatchQueue(label: "reject") - validProducts = ThreadSafe<[String: SKProduct]>() + validProducts = ThreadSafe<[String: SKProduct]>([:]) super.init() addTransactionObserver() } @@ -195,7 +193,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver reject: @escaping RCTPromiseRejectBlock = { _, _, _ in } ) { pendingTransactionWithAutoFinish = andDangerouslyFinishTransactionAutomatically - if let product = validProducts[sku] { + if let product = validProducts.value[sku] { addPromise(forKey: product.productIdentifier, resolve: resolve, reject: reject) let payment = SKMutablePayment(product: product) @@ -360,7 +358,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver } var items: [[String: Any?]] = [[:]] - for product in validProducts.values { + for product in validProducts.value.values { items.append(getProductObject(product)) } From 8286f5ca4a45a6949b12aa6a96d079f816b416d6 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Mon, 13 Nov 2023 11:19:17 -0300 Subject: [PATCH 09/16] fix: lint errors --- ios/LatestPromiseKeeper.swift | 2 +- ios/RNIapIos.swift | 6 +++--- ios/RNIapIosSk2.swift | 4 ---- ios/ThreadSafe.swift | 10 +++++----- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ios/LatestPromiseKeeper.swift b/ios/LatestPromiseKeeper.swift index 22ea16d9e..26ef872c8 100644 --- a/ios/LatestPromiseKeeper.swift +++ b/ios/LatestPromiseKeeper.swift @@ -35,4 +35,4 @@ class LatestPromiseKeeper { } } } -} \ No newline at end of file +} diff --git a/ios/RNIapIos.swift b/ios/RNIapIos.swift index 6915e650c..d3ea6d357 100644 --- a/ios/RNIapIos.swift +++ b/ios/RNIapIos.swift @@ -8,7 +8,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver private var hasListeners = false private var pendingTransactionWithAutoFinish = false private var receiptBlock: ((Data?, Error?) -> Void)? // Block to handle request the receipt async from delegate - private var validProducts: ThreadSafe<[String: SKProduct]> + private var validProducts: ThreadSafe<[String: SKProduct]> private var promotedPayment: SKPayment? private var promotedProduct: SKProduct? private var productsRequest: SKProductsRequest? @@ -193,7 +193,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver reject: @escaping RCTPromiseRejectBlock = { _, _, _ in } ) { pendingTransactionWithAutoFinish = andDangerouslyFinishTransactionAutomatically - if let product = validProducts.value[sku] { + if let product = validProducts.value[sku] { addPromise(forKey: product.productIdentifier, resolve: resolve, reject: reject) let payment = SKMutablePayment(product: product) @@ -352,7 +352,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver // StoreKitDelegate func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { - // Add received products + // Add received products for prod in response.products { add(prod) } diff --git a/ios/RNIapIosSk2.swift b/ios/RNIapIosSk2.swift index 35a092dc1..15003f891 100644 --- a/ios/RNIapIosSk2.swift +++ b/ios/RNIapIosSk2.swift @@ -277,10 +277,6 @@ class RNIapIosSk2: RCTEventEmitter, Sk2Delegate { delegate.stopObserving() } - override func addListener(_ eventName: String?) { - super.addListener(eventName) - } - /** "iap-transaction-updated" is unique to Sk2. "iap-promoted-product" is only avaiable on Sk1 diff --git a/ios/ThreadSafe.swift b/ios/ThreadSafe.swift index cce277f89..f90c0c30e 100644 --- a/ios/ThreadSafe.swift +++ b/ios/ThreadSafe.swift @@ -1,18 +1,18 @@ class ThreadSafe { private var _value: A private let queue = DispatchQueue(label: "ThreadSafe") - + init(_ value: A) { self._value = value } - + var value: A { return queue.sync { _value } } - - func atomically(_ transform: (inout A) -> ()) { + + func atomically(_ transform: (inout A) -> Void) { queue.sync { transform(&self._value) } } -} \ No newline at end of file +} From 5a5c61f2966345a2652c8e115f08aaa8f67bbd44 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Mon, 13 Nov 2023 11:33:33 -0300 Subject: [PATCH 10/16] chore: remove unecessary comments --- ios/RNIapIos.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/ios/RNIapIos.swift b/ios/RNIapIos.swift index d3ea6d357..f803b2063 100644 --- a/ios/RNIapIos.swift +++ b/ios/RNIapIos.swift @@ -167,7 +167,6 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver if let productsRequest = productsRequest { productsRequest.delegate = self - // Update latestPromiseKeeper with the new request and promise self.latestPromiseKeeper.setLatestPromise(request: productsRequest, resolve: resolve, reject: reject) productsRequest.start() @@ -352,7 +351,6 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver // StoreKitDelegate func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { - // Add received products for prod in response.products { add(prod) } From 7c0278743bc66bc2719f6d67306dd904aa5d0b53 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Mon, 13 Nov 2023 12:52:52 -0300 Subject: [PATCH 11/16] feat: add swift files to project target --- ios/RNIapIos.xcodeproj/project.pbxproj | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ios/RNIapIos.xcodeproj/project.pbxproj b/ios/RNIapIos.xcodeproj/project.pbxproj index 26efff34d..556861f77 100644 --- a/ios/RNIapIos.xcodeproj/project.pbxproj +++ b/ios/RNIapIos.xcodeproj/project.pbxproj @@ -7,8 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 5E555C0D2413F4C50049A1A2 /* RNIapIos.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNIapIos.m */; }; - F4FF95D7245B92E800C19C63 /* RNIapIos.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* RNIapIos.swift */; }; + CBA2290C2B027DA500C780F8 /* ThreadSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA2290A2B027DA500C780F8 /* ThreadSafe.swift */; }; + CBA2290D2B027DA500C780F8 /* LatestPromiseKeeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA2290B2B027DA500C780F8 /* LatestPromiseKeeper.swift */; }; + F4FF95D7245B92E800C19C63 /* RNIapIos.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* RNIapIos.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -26,6 +27,8 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libRNIapIos.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNIapIos.a; sourceTree = BUILT_PRODUCTS_DIR; }; B3E7B5891CC2AC0600A0062D /* RNIapIos.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNIapIos.m; sourceTree = ""; }; + CBA2290A2B027DA500C780F8 /* ThreadSafe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafe.swift; sourceTree = ""; }; + CBA2290B2B027DA500C780F8 /* LatestPromiseKeeper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LatestPromiseKeeper.swift; sourceTree = ""; }; F4FF95D5245B92E700C19C63 /* RNIapIosIap-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNIapIosIap-Bridging-Header.h"; sourceTree = ""; }; F4FF95D6245B92E800C19C63 /* RNIapIos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNIapIos.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -52,6 +55,8 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + CBA2290B2B027DA500C780F8 /* LatestPromiseKeeper.swift */, + CBA2290A2B027DA500C780F8 /* ThreadSafe.swift */, F4FF95D6245B92E800C19C63 /* RNIapIos.swift */, B3E7B5891CC2AC0600A0062D /* RNIapIos.m */, F4FF95D5245B92E700C19C63 /* RNIapIosIap-Bridging-Header.h */, @@ -116,8 +121,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CBA2290C2B027DA500C780F8 /* ThreadSafe.swift in Sources */, + CBA2290D2B027DA500C780F8 /* LatestPromiseKeeper.swift in Sources */, F4FF95D7245B92E800C19C63 /* RNIapIos.swift in Sources */, - B3E7B58A1CC2AC0600A0062D /* RNIapIos.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 263118f9420c075e42a2be2935335ad8c4e9bed0 Mon Sep 17 00:00:00 2001 From: Arthur Geron Date: Mon, 13 Nov 2023 15:48:37 -0300 Subject: [PATCH 12/16] feat: migrate iap example to react native 0.72 --- IapExample/.ruby-version | 2 +- IapExample/Gemfile | 5 +- IapExample/android/app/_BUCK | 55 - IapExample/android/app/build.gradle | 326 +- IapExample/android/app/build_defs.bzl | 19 - .../com/iapexample/ReactNativeFlipper.java | 6 +- .../android/app/src/main/AndroidManifest.xml | 3 +- .../java/com/iapexample/MainActivity.java | 46 +- .../java/com/iapexample/MainApplication.java | 65 +- .../com/iapexample/ReactNativeFlipper.java | 20 + .../MainApplicationReactNativeHost.java | 116 - .../components/MainComponentsRegistry.java | 36 - ...ApplicationTurboModuleManagerDelegate.java | 48 - .../android/app/src/main/jni/Android.mk | 48 - .../jni/MainApplicationModuleProvider.cpp | 24 - .../main/jni/MainApplicationModuleProvider.h | 16 - ...nApplicationTurboModuleManagerDelegate.cpp | 45 - ...ainApplicationTurboModuleManagerDelegate.h | 38 - .../src/main/jni/MainComponentsRegistry.cpp | 61 - .../android/app/src/main/jni/OnLoad.cpp | 11 - .../res/drawable/rn_edit_text_material.xml | 2 +- IapExample/android/build.gradle | 57 +- IapExample/android/gradle.properties | 6 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +- IapExample/android/gradlew | 18 +- IapExample/android/gradlew.bat | 15 +- IapExample/android/settings.gradle | 9 +- IapExample/babel.config.js | 17 - IapExample/ios/.xcode.env | 1 + .../ios/IapExample.xcodeproj/project.pbxproj | 21 +- IapExample/ios/IapExample/AppDelegate.h | 6 +- IapExample/ios/IapExample/AppDelegate.mm | 117 +- IapExample/ios/Podfile | 49 +- IapExample/ios/Podfile.lock | 789 +-- IapExample/jest.config.js | 3 + IapExample/metro.config.js | 47 +- IapExample/package.json | 42 +- IapExample/tsconfig.json | 3 + IapExample/yarn.lock | 4439 +++++++++-------- 40 files changed, 3140 insertions(+), 3494 deletions(-) delete mode 100644 IapExample/android/app/_BUCK delete mode 100644 IapExample/android/app/build_defs.bzl create mode 100644 IapExample/android/app/src/main/java/com/iapexample/ReactNativeFlipper.java delete mode 100644 IapExample/android/app/src/main/java/com/iapexample/newarchitecture/MainApplicationReactNativeHost.java delete mode 100644 IapExample/android/app/src/main/java/com/iapexample/newarchitecture/components/MainComponentsRegistry.java delete mode 100644 IapExample/android/app/src/main/java/com/iapexample/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java delete mode 100644 IapExample/android/app/src/main/jni/Android.mk delete mode 100644 IapExample/android/app/src/main/jni/MainApplicationModuleProvider.cpp delete mode 100644 IapExample/android/app/src/main/jni/MainApplicationModuleProvider.h delete mode 100644 IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp delete mode 100644 IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h delete mode 100644 IapExample/android/app/src/main/jni/MainComponentsRegistry.cpp delete mode 100644 IapExample/android/app/src/main/jni/OnLoad.cpp delete mode 100644 IapExample/babel.config.js create mode 100644 IapExample/ios/.xcode.env create mode 100644 IapExample/jest.config.js create mode 100644 IapExample/tsconfig.json diff --git a/IapExample/.ruby-version b/IapExample/.ruby-version index e4604e3af..a04abec91 100644 --- a/IapExample/.ruby-version +++ b/IapExample/.ruby-version @@ -1 +1 @@ -3.2.1 +2.6.10 diff --git a/IapExample/Gemfile b/IapExample/Gemfile index eb515b569..6a7d5c7a4 100644 --- a/IapExample/Gemfile +++ b/IapExample/Gemfile @@ -1,6 +1,7 @@ source 'https://rubygems.org' # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version -ruby '3.2.1' +ruby ">= 2.6.10" -gem 'cocoapods', '~> 1.11', '>= 1.11.2' +gem 'cocoapods', '~> 1.13' +gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' diff --git a/IapExample/android/app/_BUCK b/IapExample/android/app/_BUCK deleted file mode 100644 index c4ae17219..000000000 --- a/IapExample/android/app/_BUCK +++ /dev/null @@ -1,55 +0,0 @@ -# To learn about Buck see [Docs](https://buckbuild.com/). -# To run your application with Buck: -# - install Buck -# - `npm start` - to start the packager -# - `cd android` -# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` -# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck -# - `buck install -r android/app` - compile, install and run application -# - -load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") - -lib_deps = [] - -create_aar_targets(glob(["libs/*.aar"])) - -create_jar_targets(glob(["libs/*.jar"])) - -android_library( - name = "all-libs", - exported_deps = lib_deps, -) - -android_library( - name = "app-code", - srcs = glob([ - "src/main/java/**/*.java", - ]), - deps = [ - ":all-libs", - ":build_config", - ":res", - ], -) - -android_build_config( - name = "build_config", - package = "com.iapexample", -) - -android_resource( - name = "res", - package = "com.iapexample", - res = "src/main/res", -) - -android_binary( - name = "app", - keystore = "//android/keystores:debug", - manifest = "src/main/AndroidManifest.xml", - package_type = "debug", - deps = [ - ":app-code", - ], -) diff --git a/IapExample/android/app/build.gradle b/IapExample/android/app/build.gradle index ed9a5aac2..3d8628b67 100644 --- a/IapExample/android/app/build.gradle +++ b/IapExample/android/app/build.gradle @@ -1,222 +1,85 @@ apply plugin: "com.android.application" - -import com.android.build.OutputFile - -/** - * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets - * and bundleReleaseJsAndAssets). - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "../../node_modules/react-native/react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation. If none specified and - * // "index.android.js" exists, it will be used. Otherwise "index.js" is - * // default. Can be overridden with ENTRY_FILE environment variable. - * entryFile: "index.android.js", - * - * // https://reactnative.dev/docs/performance#enable-the-ram-format - * bundleCommand: "ram-bundle", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // whether to bundle JS and assets in another build variant (if configured). - * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants - * // The configuration property can be in the following formats - * // 'bundleIn${productFlavor}${buildType}' - * // 'bundleIn${buildType}' - * // bundleInFreeDebug: true, - * // bundleInPaidRelease: true, - * // bundleInBeta: true, - * - * // whether to disable dev mode in custom build variants (by default only disabled in release) - * // for example: to disable dev mode in the staging build type (if configured) - * devDisabledInStaging: true, - * // The configuration property can be in the following formats - * // 'devDisabledIn${productFlavor}${buildType}' - * // 'devDisabledIn${buildType}' - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"], - * - * // override which node gets called and with what additional arguments - * nodeExecutableAndArgs: ["node"], - * - * // supply additional arguments to the packager - * extraPackagerArgs: [] - * ] - */ - -project.ext.react = [ - entryFile: "index.tsx", - enableHermes: false, // clean and rebuild if changing -] - -apply from: "../../node_modules/react-native/react.gradle" +apply plugin: "com.facebook.react" /** - * Set this to true to create two separate APKs instead of one: - * - An APK that only works on ARM devices - * - An APK that only works on x86 devices - * The advantage is the size of the APK is reduced by about 4MB. - * Upload all the APKs to the Play Store and people will download - * the correct one based on the CPU architecture of their device. + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. */ -def enableSeparateBuildPerCPUArchitecture = false +react { + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '..' + // root = file("../") + // The folder where the react-native NPM package is. Default is ../node_modules/react-native + // reactNativeDir = file("../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen + // codegenDir = file("../node_modules/@react-native/codegen") + // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js + // cliFile = file("../node_modules/react-native/cli.js") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + // + // The command to run when bundling. By default is 'bundle' + // bundleCommand = "ram-bundle" + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] +} /** - * Run Proguard to shrink the Java bytecode in release builds. + * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ def enableProguardInReleaseBuilds = false /** - * The preferred build flavor of JavaScriptCore. + * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that + * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' -/** - * Whether to enable the Hermes VM. - * - * This should be set on project.ext.react and that value will be read here. If it is not set - * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode - * and the benefits of using Hermes will therefore be sharply reduced. - */ -def enableHermes = project.ext.react.get("enableHermes", false); - -/** - * Architectures to build native code for. - */ -def reactNativeArchitectures() { - def value = project.getProperties().get("reactNativeArchitectures") - return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] -} - android { ndkVersion rootProject.ext.ndkVersion compileSdkVersion rootProject.ext.compileSdkVersion + namespace "com.iapexample" defaultConfig { applicationId "com.iapexample" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" - buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() - - if (isNewArchitectureEnabled()) { - // We configure the NDK build only if you decide to opt-in for the New Architecture. - externalNativeBuild { - ndkBuild { - arguments "APP_PLATFORM=android-21", - "APP_STL=c++_shared", - "NDK_TOOLCHAIN_VERSION=clang", - "GENERATED_SRC_DIR=$buildDir/generated/source", - "PROJECT_BUILD_DIR=$buildDir", - "REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", - "REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build", - "NODE_MODULES_DIR=$rootDir/../node_modules" - cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1" - cppFlags "-std=c++17" - // Make sure this target name is the same you specify inside the - // src/main/jni/Android.mk file for the `LOCAL_MODULE` variable. - targets "iapexample_appmodules" - } - } - if (!enableSeparateBuildPerCPUArchitecture) { - ndk { - abiFilters (*reactNativeArchitectures()) - } - } - } - } - - if (isNewArchitectureEnabled()) { - // We configure the NDK build only if you decide to opt-in for the New Architecture. - externalNativeBuild { - ndkBuild { - path "$projectDir/src/main/jni/Android.mk" - } - } - def reactAndroidProjectDir = project(':ReactAndroid').projectDir - def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { - dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") - from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") - into("$buildDir/react-ndk/exported") - } - def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { - dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") - from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") - into("$buildDir/react-ndk/exported") - } - afterEvaluate { - // If you wish to add a custom TurboModule or component locally, - // you should uncomment this line. - // preBuild.dependsOn("generateCodegenArtifactsFromSchema") - preDebugBuild.dependsOn(packageReactNdkDebugLibs) - preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) - - // Due to a bug inside AGP, we have to explicitly set a dependency - // between configureNdkBuild* tasks and the preBuild tasks. - // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 - configureNdkBuildRelease.dependsOn(preReleaseBuild) - configureNdkBuildDebug.dependsOn(preDebugBuild) - reactNativeArchitectures().each { architecture -> - tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure { - dependsOn("preDebugBuild") - } - tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure { - dependsOn("preReleaseBuild") - } - } - } - } - - splits { - abi { - reset() - enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK - include (*reactNativeArchitectures()) - } } signingConfigs { debug { @@ -238,98 +101,23 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } - - flavorDimensions "appstore" - - productFlavors { - googlePlay { - dimension "appstore" - missingDimensionStrategy "store", "play" - } - - amazon { - dimension "appstore" - missingDimensionStrategy "store", "amazon" - } - } - - // applicationVariants are e.g. debug, release - applicationVariants.all { variant -> - variant.outputs.each { output -> - // For each separate APK per architecture, set a unique version code as described here: - // https://developer.android.com/studio/build/configure-apk-splits.html - // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. - def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] - def abi = output.getFilter(OutputFile.ABI) - if (abi != null) { // null for the universal-debug, universal-release variants - output.versionCodeOverride = - defaultConfig.versionCode * 1000 + versionCodes.get(abi) - } - - } - } } dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" // From node_modules - - implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - - debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { - exclude group:'com.facebook.fbjni' - } + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { - exclude group:'com.facebook.flipper' exclude group:'com.squareup.okhttp3', module:'okhttp' } - debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { - exclude group:'com.facebook.flipper' - } - - if (enableHermes) { - //noinspection GradleDynamicVersion - implementation("com.facebook.react:hermes-engine:+") { // From node_modules - exclude group:'com.facebook.fbjni' - } + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } } -if (isNewArchitectureEnabled()) { - // If new architecture is enabled, we let you build RN from source - // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. - // This will be applied to all the imported transtitive dependency. - configurations.all { - resolutionStrategy.dependencySubstitution { - substitute(module("com.facebook.react:react-native")) - .using(project(":ReactAndroid")) - .because("On New Architecture we're building React Native from source") - substitute(module("com.facebook.react:hermes-engine")) - .using(project(":ReactAndroid:hermes-engine")) - .because("On New Architecture we're building Hermes from source") - } - } -} - -// Run this once to be able to run the application with BUCK -// puts all compile dependencies into folder libs for BUCK to use -task copyDownloadableDepsToLibs(type: Copy) { - from configurations.implementation - into 'libs' -} - apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) - -def isNewArchitectureEnabled() { - // To opt-in for the New Architecture, you can either: - // - Set `newArchEnabled` to true inside the `gradle.properties` file - // - Invoke gradle with `-newArchEnabled=true` - // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` - return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" -} diff --git a/IapExample/android/app/build_defs.bzl b/IapExample/android/app/build_defs.bzl deleted file mode 100644 index fff270f8d..000000000 --- a/IapExample/android/app/build_defs.bzl +++ /dev/null @@ -1,19 +0,0 @@ -"""Helper definitions to glob .aar and .jar targets""" - -def create_aar_targets(aarfiles): - for aarfile in aarfiles: - name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] - lib_deps.append(":" + name) - android_prebuilt_aar( - name = name, - aar = aarfile, - ) - -def create_jar_targets(jarfiles): - for jarfile in jarfiles: - name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] - lib_deps.append(":" + name) - prebuilt_jar( - name = name, - binary_jar = jarfile, - ) diff --git a/IapExample/android/app/src/debug/java/com/iapexample/ReactNativeFlipper.java b/IapExample/android/app/src/debug/java/com/iapexample/ReactNativeFlipper.java index 52a510df0..f5c207d02 100644 --- a/IapExample/android/app/src/debug/java/com/iapexample/ReactNativeFlipper.java +++ b/IapExample/android/app/src/debug/java/com/iapexample/ReactNativeFlipper.java @@ -17,7 +17,6 @@ import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; -import com.facebook.flipper.plugins.react.ReactFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; import com.facebook.react.ReactInstanceEventListener; import com.facebook.react.ReactInstanceManager; @@ -25,13 +24,16 @@ import com.facebook.react.modules.network.NetworkingModule; import okhttp3.OkHttpClient; +/** + * Class responsible of loading Flipper inside your React Native application. This is the debug + * flavor of it. Here you can add your own plugins and customize the Flipper setup. + */ public class ReactNativeFlipper { public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { if (FlipperUtils.shouldEnableFlipper(context)) { final FlipperClient client = AndroidFlipperClient.getInstance(context); client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); - client.addPlugin(new ReactFlipperPlugin()); client.addPlugin(new DatabasesFlipperPlugin(context)); client.addPlugin(new SharedPreferencesFlipperPlugin(context)); client.addPlugin(CrashReporterPlugin.getInstance()); diff --git a/IapExample/android/app/src/main/AndroidManifest.xml b/IapExample/android/app/src/main/AndroidManifest.xml index 1f246b423..4122f36a5 100644 --- a/IapExample/android/app/src/main/AndroidManifest.xml +++ b/IapExample/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/IapExample/android/app/src/main/java/com/iapexample/MainActivity.java b/IapExample/android/app/src/main/java/com/iapexample/MainActivity.java index 6af7e90f2..d1a669640 100644 --- a/IapExample/android/app/src/main/java/com/iapexample/MainActivity.java +++ b/IapExample/android/app/src/main/java/com/iapexample/MainActivity.java @@ -1,21 +1,12 @@ package com.iapexample; -import android.os.Bundle; - -import com.dooboolab.rniap.RNIapActivityListener; import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; +import com.facebook.react.defaults.DefaultReactActivityDelegate; public class MainActivity extends ReactActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - //Needed for Amazon IAP - RNIapActivityListener.registerActivity(this); - } - /** * Returns the name of the main component registered from JavaScript. This is used to schedule * rendering of the component. @@ -26,33 +17,16 @@ protected String getMainComponentName() { } /** - * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and - * you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer - * (Paper). + * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link + * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React + * (aka React 18) with two boolean flags. */ @Override protected ReactActivityDelegate createReactActivityDelegate() { - return new MainActivityDelegate(this, getMainComponentName()); - } - - public static class MainActivityDelegate extends ReactActivityDelegate { - public MainActivityDelegate(ReactActivity activity, String mainComponentName) { - super(activity, mainComponentName); - } - - @Override - protected ReactRootView createRootView() { - ReactRootView reactRootView = new ReactRootView(getContext()); - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); - return reactRootView; - } - - @Override - protected boolean isConcurrentRootEnabled() { - // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). - // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } + return new DefaultReactActivityDelegate( + this, + getMainComponentName(), + // If you opted-in for the New Architecture, we enable the Fabric Renderer. + DefaultNewArchitectureEntryPoint.getFabricEnabled()); } } diff --git a/IapExample/android/app/src/main/java/com/iapexample/MainApplication.java b/IapExample/android/app/src/main/java/com/iapexample/MainApplication.java index 7b606ec37..b6db96b0b 100644 --- a/IapExample/android/app/src/main/java/com/iapexample/MainApplication.java +++ b/IapExample/android/app/src/main/java/com/iapexample/MainApplication.java @@ -1,22 +1,19 @@ package com.iapexample; import android.app.Application; -import android.content.Context; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; -import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; -import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; +import com.facebook.react.defaults.DefaultReactNativeHost; import com.facebook.soloader.SoLoader; -import com.iapexample.newarchitecture.MainApplicationReactNativeHost; -import java.lang.reflect.InvocationTargetException; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = - new ReactNativeHost(this) { + new DefaultReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; @@ -35,57 +32,31 @@ protected List getPackages() { protected String getJSMainModuleName() { return "index"; } - }; - private final ReactNativeHost mNewArchitectureNativeHost = - new MainApplicationReactNativeHost(this); + @Override + protected boolean isNewArchEnabled() { + return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + } + + @Override + protected Boolean isHermesEnabled() { + return BuildConfig.IS_HERMES_ENABLED; + } + }; @Override public ReactNativeHost getReactNativeHost() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - return mNewArchitectureNativeHost; - } else { - return mReactNativeHost; - } + return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); - // If you opted-in for the New Architecture, we enable the TurboModule system - ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; SoLoader.init(this, /* native exopackage */ false); - initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); - } - - /** - * Loads Flipper in React Native templates. Call this in the onCreate method with something like - * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); - * - * @param context - * @param reactInstanceManager - */ - private static void initializeFlipper( - Context context, ReactInstanceManager reactInstanceManager) { - if (BuildConfig.DEBUG) { - try { - /* - We use reflection here to pick up the class that initializes Flipper, - since Flipper library is not available in release mode - */ - Class aClass = Class.forName("com.iapexample.ReactNativeFlipper"); - aClass - .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) - .invoke(null, context, reactInstanceManager); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + DefaultNewArchitectureEntryPoint.load(); } + ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); } } diff --git a/IapExample/android/app/src/main/java/com/iapexample/ReactNativeFlipper.java b/IapExample/android/app/src/main/java/com/iapexample/ReactNativeFlipper.java new file mode 100644 index 000000000..2b474358e --- /dev/null +++ b/IapExample/android/app/src/main/java/com/iapexample/ReactNativeFlipper.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.iapexample; + +import android.content.Context; +import com.facebook.react.ReactInstanceManager; + +/** + * Class responsible of loading Flipper inside your React Native application. This is the release + * flavor of it so it's empty as we don't want to load Flipper. + */ +public class ReactNativeFlipper { + public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { + // Do nothing as we don't want to initialize Flipper on Release. + } +} diff --git a/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/MainApplicationReactNativeHost.java b/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/MainApplicationReactNativeHost.java deleted file mode 100644 index 0ec5300c4..000000000 --- a/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/MainApplicationReactNativeHost.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.iapexample.newarchitecture; - -import android.app.Application; -import androidx.annotation.NonNull; -import com.facebook.react.PackageList; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.ReactPackageTurboModuleManagerDelegate; -import com.facebook.react.bridge.JSIModulePackage; -import com.facebook.react.bridge.JSIModuleProvider; -import com.facebook.react.bridge.JSIModuleSpec; -import com.facebook.react.bridge.JSIModuleType; -import com.facebook.react.bridge.JavaScriptContextHolder; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.react.fabric.CoreComponentsRegistry; -import com.facebook.react.fabric.FabricJSIModuleProvider; -import com.facebook.react.fabric.ReactNativeConfig; -import com.facebook.react.uimanager.ViewManagerRegistry; -import com.iapexample.BuildConfig; -import com.iapexample.newarchitecture.components.MainComponentsRegistry; -import com.iapexample.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate; -import java.util.ArrayList; -import java.util.List; - -/** - * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both - * TurboModule delegates and the Fabric Renderer. - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. - */ -public class MainApplicationReactNativeHost extends ReactNativeHost { - public MainApplicationReactNativeHost(Application application) { - super(application); - } - - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - // TurboModules must also be loaded here providing a valid TurboReactPackage implementation: - // packages.add(new TurboReactPackage() { ... }); - // If you have custom Fabric Components, their ViewManagers should also be loaded here - // inside a ReactPackage. - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @NonNull - @Override - protected ReactPackageTurboModuleManagerDelegate.Builder - getReactPackageTurboModuleManagerDelegateBuilder() { - // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary - // for the new architecture and to use TurboModules correctly. - return new MainApplicationTurboModuleManagerDelegate.Builder(); - } - - @Override - protected JSIModulePackage getJSIModulePackage() { - return new JSIModulePackage() { - @Override - public List getJSIModules( - final ReactApplicationContext reactApplicationContext, - final JavaScriptContextHolder jsContext) { - final List specs = new ArrayList<>(); - - // Here we provide a new JSIModuleSpec that will be responsible of providing the - // custom Fabric Components. - specs.add( - new JSIModuleSpec() { - @Override - public JSIModuleType getJSIModuleType() { - return JSIModuleType.UIManager; - } - - @Override - public JSIModuleProvider getJSIModuleProvider() { - final ComponentFactory componentFactory = new ComponentFactory(); - CoreComponentsRegistry.register(componentFactory); - - // Here we register a Components Registry. - // The one that is generated with the template contains no components - // and just provides you the one from React Native core. - MainComponentsRegistry.register(componentFactory); - - final ReactInstanceManager reactInstanceManager = getReactInstanceManager(); - - ViewManagerRegistry viewManagerRegistry = - new ViewManagerRegistry( - reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)); - - return new FabricJSIModuleProvider( - reactApplicationContext, - componentFactory, - ReactNativeConfig.DEFAULT_CONFIG, - viewManagerRegistry); - } - }); - return specs; - } - }; - } -} diff --git a/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/components/MainComponentsRegistry.java b/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/components/MainComponentsRegistry.java deleted file mode 100644 index 28001dcc8..000000000 --- a/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/components/MainComponentsRegistry.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.iapexample.newarchitecture.components; - -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.soloader.SoLoader; - -/** - * Class responsible to load the custom Fabric Components. This class has native methods and needs a - * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ - * folder for you). - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. - */ -@DoNotStrip -public class MainComponentsRegistry { - static { - SoLoader.loadLibrary("fabricjni"); - } - - @DoNotStrip private final HybridData mHybridData; - - @DoNotStrip - private native HybridData initHybrid(ComponentFactory componentFactory); - - @DoNotStrip - private MainComponentsRegistry(ComponentFactory componentFactory) { - mHybridData = initHybrid(componentFactory); - } - - @DoNotStrip - public static MainComponentsRegistry register(ComponentFactory componentFactory) { - return new MainComponentsRegistry(componentFactory); - } -} diff --git a/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java b/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java deleted file mode 100644 index 90f85b531..000000000 --- a/IapExample/android/app/src/main/java/com/iapexample/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.iapexample.newarchitecture.modules; - -import com.facebook.jni.HybridData; -import com.facebook.react.ReactPackage; -import com.facebook.react.ReactPackageTurboModuleManagerDelegate; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.soloader.SoLoader; -import java.util.List; - -/** - * Class responsible to load the TurboModules. This class has native methods and needs a - * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ - * folder for you). - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. - */ -public class MainApplicationTurboModuleManagerDelegate - extends ReactPackageTurboModuleManagerDelegate { - - private static volatile boolean sIsSoLibraryLoaded; - - protected MainApplicationTurboModuleManagerDelegate( - ReactApplicationContext reactApplicationContext, List packages) { - super(reactApplicationContext, packages); - } - - protected native HybridData initHybrid(); - - native boolean canCreateTurboModule(String moduleName); - - public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder { - protected MainApplicationTurboModuleManagerDelegate build( - ReactApplicationContext context, List packages) { - return new MainApplicationTurboModuleManagerDelegate(context, packages); - } - } - - @Override - protected synchronized void maybeLoadOtherSoLibraries() { - if (!sIsSoLibraryLoaded) { - // If you change the name of your application .so file in the Android.mk file, - // make sure you update the name here as well. - SoLoader.loadLibrary("iapexample_appmodules"); - sIsSoLibraryLoaded = true; - } - } -} diff --git a/IapExample/android/app/src/main/jni/Android.mk b/IapExample/android/app/src/main/jni/Android.mk deleted file mode 100644 index 579dc7080..000000000 --- a/IapExample/android/app/src/main/jni/Android.mk +++ /dev/null @@ -1,48 +0,0 @@ -THIS_DIR := $(call my-dir) - -include $(REACT_ANDROID_DIR)/Android-prebuilt.mk - -# If you wish to add a custom TurboModule or Fabric component in your app you -# will have to include the following autogenerated makefile. -# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk -include $(CLEAR_VARS) - -LOCAL_PATH := $(THIS_DIR) - -# You can customize the name of your application .so file here. -LOCAL_MODULE := iapexample_appmodules - -LOCAL_C_INCLUDES := $(LOCAL_PATH) -LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) - -# If you wish to add a custom TurboModule or Fabric component in your app you -# will have to uncomment those lines to include the generated source -# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni) -# -# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni -# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp) -# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni - -# Here you should add any native library you wish to depend on. -LOCAL_SHARED_LIBRARIES := \ - libfabricjni \ - libfbjni \ - libfolly_runtime \ - libglog \ - libjsi \ - libreact_codegen_rncore \ - libreact_debug \ - libreact_nativemodule_core \ - libreact_render_componentregistry \ - libreact_render_core \ - libreact_render_debug \ - libreact_render_graphics \ - librrc_view \ - libruntimeexecutor \ - libturbomodulejsijni \ - libyoga - -LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall - -include $(BUILD_SHARED_LIBRARY) diff --git a/IapExample/android/app/src/main/jni/MainApplicationModuleProvider.cpp b/IapExample/android/app/src/main/jni/MainApplicationModuleProvider.cpp deleted file mode 100644 index 0ac23cc62..000000000 --- a/IapExample/android/app/src/main/jni/MainApplicationModuleProvider.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "MainApplicationModuleProvider.h" - -#include - -namespace facebook { -namespace react { - -std::shared_ptr MainApplicationModuleProvider( - const std::string moduleName, - const JavaTurboModule::InitParams ¶ms) { - // Here you can provide your own module provider for TurboModules coming from - // either your application or from external libraries. The approach to follow - // is similar to the following (for a library called `samplelibrary`: - // - // auto module = samplelibrary_ModuleProvider(moduleName, params); - // if (module != nullptr) { - // return module; - // } - // return rncore_ModuleProvider(moduleName, params); - return rncore_ModuleProvider(moduleName, params); -} - -} // namespace react -} // namespace facebook diff --git a/IapExample/android/app/src/main/jni/MainApplicationModuleProvider.h b/IapExample/android/app/src/main/jni/MainApplicationModuleProvider.h deleted file mode 100644 index 0fa43fa69..000000000 --- a/IapExample/android/app/src/main/jni/MainApplicationModuleProvider.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace facebook { -namespace react { - -std::shared_ptr MainApplicationModuleProvider( - const std::string moduleName, - const JavaTurboModule::InitParams ¶ms); - -} // namespace react -} // namespace facebook diff --git a/IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp b/IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp deleted file mode 100644 index dbbdc3d13..000000000 --- a/IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "MainApplicationTurboModuleManagerDelegate.h" -#include "MainApplicationModuleProvider.h" - -namespace facebook { -namespace react { - -jni::local_ref -MainApplicationTurboModuleManagerDelegate::initHybrid( - jni::alias_ref) { - return makeCxxInstance(); -} - -void MainApplicationTurboModuleManagerDelegate::registerNatives() { - registerHybrid({ - makeNativeMethod( - "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), - makeNativeMethod( - "canCreateTurboModule", - MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), - }); -} - -std::shared_ptr -MainApplicationTurboModuleManagerDelegate::getTurboModule( - const std::string name, - const std::shared_ptr jsInvoker) { - // Not implemented yet: provide pure-C++ NativeModules here. - return nullptr; -} - -std::shared_ptr -MainApplicationTurboModuleManagerDelegate::getTurboModule( - const std::string name, - const JavaTurboModule::InitParams ¶ms) { - return MainApplicationModuleProvider(name, params); -} - -bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( - std::string name) { - return getTurboModule(name, nullptr) != nullptr || - getTurboModule(name, {.moduleName = name}) != nullptr; -} - -} // namespace react -} // namespace facebook diff --git a/IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h b/IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h deleted file mode 100644 index 32bb3a934..000000000 --- a/IapExample/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include - -#include -#include - -namespace facebook { -namespace react { - -class MainApplicationTurboModuleManagerDelegate - : public jni::HybridClass< - MainApplicationTurboModuleManagerDelegate, - TurboModuleManagerDelegate> { - public: - // Adapt it to the package you used for your Java class. - static constexpr auto kJavaDescriptor = - "Lcom/iapexample/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; - - static jni::local_ref initHybrid(jni::alias_ref); - - static void registerNatives(); - - std::shared_ptr getTurboModule( - const std::string name, - const std::shared_ptr jsInvoker) override; - std::shared_ptr getTurboModule( - const std::string name, - const JavaTurboModule::InitParams ¶ms) override; - - /** - * Test-only method. Allows user to verify whether a TurboModule can be - * created by instances of this class. - */ - bool canCreateTurboModule(std::string name); -}; - -} // namespace react -} // namespace facebook diff --git a/IapExample/android/app/src/main/jni/MainComponentsRegistry.cpp b/IapExample/android/app/src/main/jni/MainComponentsRegistry.cpp deleted file mode 100644 index 8f7edffd6..000000000 --- a/IapExample/android/app/src/main/jni/MainComponentsRegistry.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "MainComponentsRegistry.h" - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} - -std::shared_ptr -MainComponentsRegistry::sharedProviderRegistry() { - auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); - - // Custom Fabric Components go here. You can register custom - // components coming from your App or from 3rd party libraries here. - // - // providerRegistry->add(concreteComponentDescriptorProvider< - // AocViewerComponentDescriptor>()); - return providerRegistry; -} - -jni::local_ref -MainComponentsRegistry::initHybrid( - jni::alias_ref, - ComponentFactory *delegate) { - auto instance = makeCxxInstance(delegate); - - auto buildRegistryFunction = - [](EventDispatcher::Weak const &eventDispatcher, - ContextContainer::Shared const &contextContainer) - -> ComponentDescriptorRegistry::Shared { - auto registry = MainComponentsRegistry::sharedProviderRegistry() - ->createComponentDescriptorRegistry( - {eventDispatcher, contextContainer}); - - auto mutableRegistry = - std::const_pointer_cast(registry); - - mutableRegistry->setFallbackComponentDescriptor( - std::make_shared( - ComponentDescriptorParameters{ - eventDispatcher, contextContainer, nullptr})); - - return registry; - }; - - delegate->buildRegistryFunction = buildRegistryFunction; - return instance; -} - -void MainComponentsRegistry::registerNatives() { - registerHybrid({ - makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), - }); -} - -} // namespace react -} // namespace facebook diff --git a/IapExample/android/app/src/main/jni/OnLoad.cpp b/IapExample/android/app/src/main/jni/OnLoad.cpp deleted file mode 100644 index c569b6e86..000000000 --- a/IapExample/android/app/src/main/jni/OnLoad.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "MainApplicationTurboModuleManagerDelegate.h" -#include "MainComponentsRegistry.h" - -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { - return facebook::jni::initialize(vm, [] { - facebook::react::MainApplicationTurboModuleManagerDelegate:: - registerNatives(); - facebook::react::MainComponentsRegistry::registerNatives(); - }); -} diff --git a/IapExample/android/app/src/main/res/drawable/rn_edit_text_material.xml b/IapExample/android/app/src/main/res/drawable/rn_edit_text_material.xml index f35d99620..73b37e4d9 100644 --- a/IapExample/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ b/IapExample/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -20,7 +20,7 @@ android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"> -