update app_review

This commit is contained in:
2026-01-15 00:37:49 -08:00
parent 2e5f0f1ae1
commit a53ed330c5
70 changed files with 2227 additions and 2130 deletions
@@ -1,4 +0,0 @@
#import <Flutter/Flutter.h>
@interface AppReviewPlugin : NSObject<FlutterPlugin>
@end
@@ -1,8 +0,0 @@
#import "AppReviewPlugin.h"
#import <app_review/app_review-Swift.h>
@implementation AppReviewPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftAppReviewPlugin registerWithRegistrar:registrar];
}
@end
@@ -0,0 +1,134 @@
import Flutter
import StoreKit
import SwiftUI
public class AppReviewPlugin: NSObject, FlutterPlugin, AppReviewApi {
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = AppReviewPlugin()
AppReviewApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance)
}
public func requestReview(testMode: Bool, completion: @escaping (Result<String?, Error>) -> Void) {
if #available(iOS 16.0, *) {
DispatchQueue.main.async {
if let windowScene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) {
let controller = UIHostingController(rootView: ReviewRequestView())
controller.view.isHidden = true
controller.view.frame = .zero
keyWindow.addSubview(controller.view)
// Triggering onAppear by adding to hierarchy.
}
}
completion(.success("Requested Review via SwiftUI"))
} else if #available(iOS 14.0, *) {
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
completion(.success("Requested Review"))
} else {
SKStoreReviewController.requestReview()
completion(.success("Requested Review"))
}
} else if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
completion(.success("Requested Review"))
} else {
completion(.success("Review not available"))
}
}
public func isRequestReviewAvailable(completion: @escaping (Result<Bool, Error>) -> Void) {
if #available(iOS 10.3, *) {
completion(.success(true))
} else {
completion(.success(false))
}
}
public func getBundleId(completion: @escaping (Result<String, Error>) -> Void) {
completion(.success(Bundle.main.bundleIdentifier ?? ""))
}
public func openStoreListing(storeId: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let storeId = storeId, !storeId.isEmpty else {
completion(.failure(NSError(domain: "AppReview", code: 404, userInfo: [NSLocalizedDescriptionKey: "Store ID is missing"])))
return
}
let urlString = "https://apps.apple.com/app/id\(storeId)"
if let url = URL(string: urlString) {
UIApplication.shared.open(url, options: [:]) { success in
if success {
completion(.success(()))
} else {
completion(.failure(NSError(domain: "AppReview", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to open store listing"])))
}
}
} else {
completion(.failure(NSError(domain: "AppReview", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid Store URL"])))
}
}
public func openAppStoreReview(storeId: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let storeId = storeId, !storeId.isEmpty else {
completion(.failure(NSError(domain: "AppReview", code: 404, userInfo: [NSLocalizedDescriptionKey: "Store ID is missing"])))
return
}
let urlString = "https://apps.apple.com/app/id\(storeId)?action=write-review"
if let url = URL(string: urlString) {
UIApplication.shared.open(url, options: [:]) { success in
if success {
completion(.success(()))
} else {
completion(.failure(NSError(domain: "AppReview", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to open App Store review"])))
}
}
} else {
completion(.failure(NSError(domain: "AppReview", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid Store URL"])))
}
}
public func lookupAppId(bundleId: String, countryCode: String?, completion: @escaping (Result<String?, Error>) -> Void) {
let country = countryCode ?? ""
let urlString = "https://itunes.apple.com/\(country)/lookup?bundleId=\(bundleId)"
guard let url = URL(string: urlString) else {
completion(.failure(NSError(domain: "AppReview", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid Lookup URL"])))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.success(nil))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let results = json["results"] as? [[String: Any]],
let firstResult = results.first,
let trackId = firstResult["trackId"] as? Int {
completion(.success(String(trackId)))
} else {
completion(.success(nil))
}
} catch {
completion(.failure(error))
}
}
task.resume()
}
}
@available(iOS 16.0, *)
struct ReviewRequestView: View {
@Environment(\.requestReview) var requestReview
var body: some View {
EmptyView()
.onAppear {
requestReview()
}
}
}
@@ -0,0 +1,241 @@
// Autogenerated from Pigeon (v26.1.5), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
/// Error class for passing custom error details to Dart side.
final class PigeonError: Error {
let code: String
let message: String?
let details: Sendable?
init(code: String, message: String?, details: Sendable?) {
self.code = code
self.message = message
self.details = details
}
var localizedDescription: String {
return
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
}
}
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private class MessagesPigeonCodecReader: FlutterStandardReader {
}
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
}
private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return MessagesPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return MessagesPigeonCodecWriter(data: data)
}
}
class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol AppReviewApi {
/// Request review.
///
/// Tells StoreKit / Play Store to ask the user to rate or review your app, if appropriate.
/// Supported only in iOS 10.3+ and Android with Play Services installed (see [isRequestReviewAvailable]).
///
/// Returns string with details message.
func requestReview(testMode: Bool, completion: @escaping (Result<String?, Error>) -> Void)
/// Check if [requestReview] feature available.
func isRequestReviewAvailable(completion: @escaping (Result<Bool, Error>) -> Void)
/// Opens the store listing for the specified app.
///
/// [storeId] is the package name (Android) or App ID (iOS/macOS).
func openStoreListing(storeId: String?, completion: @escaping (Result<Void, Error>) -> Void)
/// Opens the App Store review page (iOS/macOS only).
///
/// [storeId] is the App ID.
func openAppStoreReview(storeId: String?, completion: @escaping (Result<Void, Error>) -> Void)
/// Returns package name for application.
func getBundleId(completion: @escaping (Result<String, Error>) -> Void)
/// Requests the App ID from the App Store (via iTunes Lookup API).
///
/// [bundleId] is the bundle identifier to look up.
/// [countryCode] is the optional country code.
func lookupAppId(bundleId: String, countryCode: String?, completion: @escaping (Result<String?, Error>) -> Void)
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class AppReviewApiSetup {
static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
/// Sets up an instance of `AppReviewApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: AppReviewApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
/// Request review.
///
/// Tells StoreKit / Play Store to ask the user to rate or review your app, if appropriate.
/// Supported only in iOS 10.3+ and Android with Play Services installed (see [isRequestReviewAvailable]).
///
/// Returns string with details message.
let requestReviewChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app_review.AppReviewApi.requestReview\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestReviewChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let testModeArg = args[0] as! Bool
api.requestReview(testMode: testModeArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestReviewChannel.setMessageHandler(nil)
}
/// Check if [requestReview] feature available.
let isRequestReviewAvailableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app_review.AppReviewApi.isRequestReviewAvailable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
isRequestReviewAvailableChannel.setMessageHandler { _, reply in
api.isRequestReviewAvailable { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
isRequestReviewAvailableChannel.setMessageHandler(nil)
}
/// Opens the store listing for the specified app.
///
/// [storeId] is the package name (Android) or App ID (iOS/macOS).
let openStoreListingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app_review.AppReviewApi.openStoreListing\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
openStoreListingChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let storeIdArg: String? = nilOrValue(args[0])
api.openStoreListing(storeId: storeIdArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
openStoreListingChannel.setMessageHandler(nil)
}
/// Opens the App Store review page (iOS/macOS only).
///
/// [storeId] is the App ID.
let openAppStoreReviewChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app_review.AppReviewApi.openAppStoreReview\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
openAppStoreReviewChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let storeIdArg: String? = nilOrValue(args[0])
api.openAppStoreReview(storeId: storeIdArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
openAppStoreReviewChannel.setMessageHandler(nil)
}
/// Returns package name for application.
let getBundleIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app_review.AppReviewApi.getBundleId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getBundleIdChannel.setMessageHandler { _, reply in
api.getBundleId { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
getBundleIdChannel.setMessageHandler(nil)
}
/// Requests the App ID from the App Store (via iTunes Lookup API).
///
/// [bundleId] is the bundle identifier to look up.
/// [countryCode] is the optional country code.
let lookupAppIdChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app_review.AppReviewApi.lookupAppId\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
lookupAppIdChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let bundleIdArg = args[0] as! String
let countryCodeArg: String? = nilOrValue(args[1])
api.lookupAppId(bundleId: bundleIdArg, countryCode: countryCodeArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
lookupAppIdChannel.setMessageHandler(nil)
}
}
}
@@ -1,39 +0,0 @@
import Flutter
import UIKit
import StoreKit
public class SwiftAppReviewPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "app_review", binaryMessenger: registrar.messenger())
let instance = SwiftAppReviewPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
var appID = "com.apple.appstore" //Your App ID on App Store
if let bundleIdentifier = Bundle.main.bundleIdentifier {
appID = bundleIdentifier
}
switch (call.method) {
case "requestReview":
//App Store Review
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview() // Requesting alert view for getting rating from the user.
result("Later than iOS 10.3 the App will Request the App Review, Users can turn this off in settings for all apps, Apple will manage when to request the review from the user. In Debug it will always show. Requesting review for: " + appID)
} else {
// Fallback on earlier versions
result("Prior to iOS 10.3 App Review from App is not available. You should go to Store Page of App: " + appID + ". If the app is not published the app will not be found.")
}
case "isRequestReviewAvailable":
if #available(iOS 10.3, *) {
result("1")
} else {
result("0")
}
default:
result(FlutterMethodNotImplemented)
}
}
}