import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'src/exceptions.dart'; import 'src/messages.g.dart'; class AppReview { static const Duration kDefaultDuration = Duration(minutes: 5); static AppReviewApi _api = AppReviewApi(); //---------------------------------------------------------------------------- // Public Interface //---------------------------------------------------------------------------- /// Returns package name for application. static Future getAppId() => getBundleName(); /// Returns package name for application. static Future getBundleName() async { if (_appBundle != null) { return _appBundle; } try { _appBundle = await _api.getBundleId(); return _appBundle; } catch (e) { return null; } } /// Returns Apple ID for iOS application. /// /// If there is no such application in App Store - returns empty string. static Future getIosAppId({ String? countryCode, String? bundleId, }) async { // If bundle name is not provided // then fetch and return the app ID from cache (if available) if (bundleId == null) { _appId ??= await getIosAppId( bundleId: await getBundleName(), countryCode: countryCode, ); return _appId; } // Else fetch from AppStore final String id = bundleId; final String country = countryCode ?? _appCountry ?? ''; String? appId; if (id.isNotEmpty) { try { appId = await _api.lookupAppId(id, country); } catch (e) { debugPrint('Error fetching app ID: $e'); } finally { if (appId?.isNotEmpty == true) { debugPrint('Track ID: $appId'); } else { debugPrint('Application with bundle $id is not found on App Store'); } } } return appId ?? ''; } /// 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]). /// /// Throws [AppReviewException] if request fails. static Future requestReview({ bool useAndroidTestMode = false, }) async { final result = await _api.requestReview(useAndroidTestMode); if (result != null && result.toLowerCase().contains("not available")) { throw AppReviewUnavailableException(result); } } /// Check if [requestReview] feature available. static Future isRequestReviewAvailable() async { if (Platform.isIOS || Platform.isAndroid || Platform.isMacOS) { try { return await _api.isRequestReviewAvailable(); } catch (e) { return false; } } return false; } /// Open store page with action write review. /// /// Supported only for iOS, on Android [storeListing] will be executed. /// /// [appStoreId] - App ID for iOS App Store (e.g. 1234567890) /// [playStoreId] - Package name for Google Play Store (e.g. com.example.app) on Android. static Future writeReview({ String? appStoreId, String? playStoreId, bool useAndroidTestMode = false, }) async { if (Platform.isIOS || Platform.isMacOS) { await _openIosReview( appId: appStoreId, compose: true, ); return; } if (Platform.isAndroid) { await _openAndroidReview( appId: playStoreId, useAndroidTestMode: useAndroidTestMode, ); return; } } /// Navigates to Store Listing in Google Play/App Store. /// /// [appStoreId] - App ID for iOS App Store (e.g. 1234567890) /// [playStoreId] - Package name for Google Play Store (e.g. com.example.app) on Android. static Future storeListing({ String? appStoreId, String? playStoreId, }) async { if (Platform.isIOS || Platform.isMacOS) { await openAppStore(appId: appStoreId); return; } if (Platform.isAndroid) { await openGooglePlay(appId: playStoreId); return; } } //---------------------------------------------------------------------------- // Helper methods //---------------------------------------------------------------------------- static String? _appCountry; static String? _appBundle; static String? _appId; /// It would be great if I could add country code into AppStore lookup URL. /// Eg: AppReview.setCountryCode('jp'); static void setCountryCode(String code) => _appCountry = code.isEmpty ? null : code; /// Require app review for iOS static Future _openIosReview({ String? appId, bool compose = false, }) async { if (compose) { final id = appId ?? (await getIosAppId()) ?? ''; try { await _api.openAppStoreReview(id); } catch (e) { throw AppReviewStoreListingFailedException(e.toString()); } return; } try { final result = await _api.requestReview(false); if (result != null && result.toLowerCase().contains("not available")) { throw AppReviewUnavailableException(result); } } catch (e) { if (e is AppReviewException) rethrow; throw AppReviewRequestFailedException(e.toString()); } } /// Require app review for Android static Future _openAndroidReview( {String? appId, bool useAndroidTestMode = false}) async { try { final result = await _api.requestReview(useAndroidTestMode); if (result != null && result.toLowerCase().contains("not available")) { throw AppReviewUnavailableException(result); } } catch (e) { if (e is AppReviewException) rethrow; // If request fails, try opening store await openGooglePlay(appId: appId); } } /// Open in AppStore static Future openAppStore({ String? fallbackUrl, String? appId, }) async { final id = appId ?? await getIosAppId() ?? ''; try { await _api.openStoreListing(id); } catch (e) { throw AppReviewStoreListingFailedException(e.toString()); } } /// Open in GooglePlay static Future openGooglePlay({ String? fallbackUrl, String? appId, }) async { final bundle = appId ?? await getBundleName() ?? ''; try { await _api.openStoreListing(bundle); } catch (e) { throw AppReviewStoreListingFailedException(e.toString()); } } }