feat: use RequestReviewAction API on iOS 16+ (#158)

* feat: use `RequestReviewAction` API on iOS 16+

* update readme
This commit is contained in:
Hugo EXTRAT 2025-03-12 16:32:39 +01:00 committed by GitHub
parent a9cdc5639c
commit 9551ea6fa8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 65 additions and 50 deletions

View File

@ -120,9 +120,12 @@ To enable in-app reviews for the Samsung Galaxy Store, ensure that your app is c
- **Development Mode**: The in-app review dialog is always displayed in development mode, regardless of rate limits. This is useful for testing purposes.
- **Best Practices**: According to operating system guidelines, it is recommended to request a review during a natural flow in your app, rather than from a button. For example, you might request a review after a user has completed a task or achieved a milestone within your app.
[Apple Documentation](https://developer.apple.com/documentation/storekit/skstorereviewcontroller/3566727-requestreview#discussion)<br>
[Apple Documentation iOS 14+](https://developer.apple.com/documentation/storekit/skstorereviewcontroller/3566727-requestreview#discussion)<br>
[Apple Documentation iOS 16+](https://developer.apple.com/documentation/storekit/requestreviewaction)<br>
[Google Documentation](https://developer.android.com/guide/playcore/in-app-review)
Below iOS 16 library is using the deprecated SKStoreReviewController and on iOS 16+ it uses the new RequestReviewAction API. Both implementations provide the same functionality and user experience.
### `RateApp.openStoreForReview(options)`
Opens the app store page for the app, allowing the user to leave a review.

View File

@ -14,7 +14,7 @@ Pod::Spec.new do |s|
s.platforms = { :ios => min_ios_version_supported }
s.source = { :git => "https://github.com/huextrat/react-native-rate-app.git", :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,cpp}"
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
s.private_header_files = "ios/generated/**/*.h"
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.

View File

@ -8,7 +8,7 @@ PODS:
- hermes-engine (0.78.0):
- hermes-engine/Pre-built (= 0.78.0)
- hermes-engine/Pre-built (0.78.0)
- RateApp (1.2.2):
- RateApp (1.2.3):
- DoubleConversion
- glog
- hermes-engine
@ -1769,7 +1769,7 @@ SPEC CHECKSUMS:
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
hermes-engine: b417d2b2aee3b89b58e63e23a51e02be91dc876d
RateApp: 4ae46fe22bc16dfa26d931ce1cb4968185eaa9d3
RateApp: d3cbe9933fd0ea0ce2b23522293bd99a85acf92b
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35
RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea

View File

@ -33,5 +33,6 @@ const styles = StyleSheet.create({
alignItems: "center",
justifyContent: "center",
gap: 8,
backgroundColor: "white",
},
});

View File

@ -0,0 +1 @@
#import <React/RCTBridgeModule.h>

View File

@ -1,55 +1,15 @@
#import "RateApp.h"
#import <StoreKit/StoreKit.h>
#import <React/RCTBridgeModule.h>
static NSString *const kNoActiveSceneError = @"no_active_scene";
static NSString *const kUnsupportedPlatformError = @"unsupported_platform";
@interface RCT_EXTERN_MODULE(RateApp, NSObject)
@implementation RateApp
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(requestReview:(RCTPromiseResolveBlock)resolve
RCT_EXTERN_METHOD(requestReview:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
UIWindowScene *scene = [self findActiveScene];
if (scene) {
[SKStoreReviewController requestReviewInScene:scene];
resolve(@(YES));
} else {
reject(kNoActiveSceneError, @"No active scene found", nil);
}
}
RCT_EXPORT_METHOD(requestReviewAppGallery:(RCTPromiseResolveBlock)resolve
RCT_EXTERN_METHOD(requestReviewAppGallery:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
// Since this is iOS, we should reject with an appropriate message
reject(kUnsupportedPlatformError, @"App Gallery reviews are not supported on iOS", nil);
}
RCT_EXPORT_METHOD(requestReviewGalaxyStore:(NSString *)packageName
RCT_EXTERN_METHOD(requestReviewGalaxyStore:(NSString *)packageName
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
// Since this is iOS, we should reject with an appropriate message
reject(kUnsupportedPlatformError, @"Galaxy Store reviews are not supported on iOS", nil);
}
- (UIWindowScene *) findActiveScene {
for (UIWindowScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive) {
return scene;
}
}
return nil;
}
// Don't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeRateAppSpecJSI>(params);
}
#endif
@end
@end

50
ios/RateApp.swift Normal file
View File

@ -0,0 +1,50 @@
import Foundation
import StoreKit
@objc(RateApp)
class RateApp: NSObject {
private let noActiveSceneError = "no_active_scene"
private let unsupportedPlatformError = "unsupported_platform"
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func requestReview(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
if let scene = findActiveScene() {
if #available(iOS 16.0, *) {
Task { @MainActor in
AppStore.requestReview(in: scene)
}
} else {
SKStoreReviewController.requestReview(in: scene)
}
resolve(true)
} else {
reject(noActiveSceneError, "No active scene found", nil)
}
}
@objc
func requestReviewAppGallery(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
reject(unsupportedPlatformError, "App Gallery reviews are not supported on iOS", nil)
}
@objc
func requestReviewGalaxyStore(_ packageName: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
reject(unsupportedPlatformError, "Galaxy Store reviews are not supported on iOS", nil)
}
private func findActiveScene() -> UIWindowScene? {
for scene in UIApplication.shared.connectedScenes {
if let windowScene = scene as? UIWindowScene,
windowScene.activationState == .foregroundActive {
return windowScene
}
}
return nil
}
}