From cbb116feec02e9120fc371c94c138a0c2d7357c8 Mon Sep 17 00:00:00 2001 From: Arunshaik2001 Date: Thu, 11 May 2023 15:19:58 +0530 Subject: [PATCH] 1. Migrated java plugin file to kotlin to use latest features. 2. Moved method names and argument names in constant file --- android/build.gradle | 4 + .../PurchasesFlutterPlugin.java | 679 ------------------ .../revenuecat/purchases_flutter/Constants.kt | 112 +++ .../PurchasesFlutterPlugin.kt | 652 +++++++++++++++++ 4 files changed, 768 insertions(+), 679 deletions(-) delete mode 100644 android/src/main/java/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.java create mode 100644 android/src/main/kotlin/com/revenuecat/purchases_flutter/Constants.kt create mode 100644 android/src/main/kotlin/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.kt diff --git a/android/build.gradle b/android/build.gradle index 57013ace5..237ced8a8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,6 +37,10 @@ android { jvmTarget = '1.8' } + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/android/src/main/java/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.java b/android/src/main/java/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.java deleted file mode 100644 index 42fe666bc..000000000 --- a/android/src/main/java/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.java +++ /dev/null @@ -1,679 +0,0 @@ -package com.revenuecat.purchases_flutter; - -import android.app.Activity; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.revenuecat.purchases.Purchases; -import com.revenuecat.purchases.PurchasesErrorCode; -import com.revenuecat.purchases.Store; -import com.revenuecat.purchases.common.PlatformInfo; -import com.revenuecat.purchases.hybridcommon.CommonKt; -import com.revenuecat.purchases.hybridcommon.ErrorContainer; -import com.revenuecat.purchases.hybridcommon.OnResult; -import com.revenuecat.purchases.hybridcommon.OnResultAny; -import com.revenuecat.purchases.hybridcommon.OnResultList; -import com.revenuecat.purchases.hybridcommon.SubscriberAttributesKt; -import com.revenuecat.purchases.hybridcommon.mappers.CustomerInfoMapperKt; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import kotlin.UninitializedPropertyAccessException; - -/** - * PurchasesFlutterPlugin - */ -public class PurchasesFlutterPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware { - private String INVALID_ARGS_ERROR_CODE = "invalidArgs"; - - private static final String CUSTOMER_INFO_UPDATED = "Purchases-CustomerInfoUpdated"; - protected static final String LOG_HANDLER_EVENT = "Purchases-LogHandlerEvent"; - - // Only set registrar for v1 embedder. - @SuppressWarnings("deprecation") - private io.flutter.plugin.common.PluginRegistry.Registrar registrar; - // Only set activity for v2 embedder. Always access activity from getActivity() method. - @Nullable private Context applicationContext; - @Nullable private MethodChannel channel; - @Nullable private Activity activity; - - private final Handler handler = new Handler(Looper.getMainLooper()); - - private static final String PLATFORM_NAME = "flutter"; - private static final String PLUGIN_VERSION = "4.13.0-SNAPSHOT"; - - /** - * Plugin registration. - */ - @SuppressWarnings("deprecation") - public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - PurchasesFlutterPlugin instance = new PurchasesFlutterPlugin(); - instance.onAttachedToEngine(registrar.messenger(), registrar.context()); - instance.registrar = registrar; - registrar.addViewDestroyListener(new io.flutter.plugin.common.PluginRegistry.ViewDestroyListener() { - @Override - public boolean onViewDestroy(io.flutter.view.FlutterNativeView flutterNativeView) { - try { - Purchases.getSharedInstance().close(); - } catch (UninitializedPropertyAccessException e) { - // there's no instance so all good - } - return false; - } - }); - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - onAttachedToEngine(binding.getBinaryMessenger(), binding.getApplicationContext()); - } - - private void onAttachedToEngine(BinaryMessenger messenger, Context applicationContext) { - this.channel = new MethodChannel(messenger, "purchases_flutter"); - this.applicationContext = applicationContext; - this.channel.setMethodCallHandler(this); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - if (channel != null) { - channel.setMethodCallHandler(null); - } - this.channel = null; - this.applicationContext = null; - } - - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - this.activity = binding.getActivity(); - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - onAttachedToActivity(binding); - } - - @Override - public void onDetachedFromActivity() { - this.activity = null; - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity(); - } - - public Activity getActivity() { - return registrar != null ? registrar.activity() : activity; - } - - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { - switch (call.method) { - case "setupPurchases": - String apiKey = call.argument("apiKey"); - String appUserId = call.argument("appUserId"); - Boolean observerMode = call.argument("observerMode"); - Boolean useAmazon = call.argument("useAmazon"); - //noinspection unused - String userDefaultsSuiteName = call.argument("userDefaultsSuiteName"); // iOS-only, unused. - //noinspection unused - Boolean usesStoreKit2IfAvailable = call.argument("usesStoreKit2IfAvailable"); // iOS-only, unused. - setupPurchases(apiKey, appUserId, observerMode, useAmazon, result); - break; - case "setFinishTransactions": - Boolean finishTransactions = call.argument("finishTransactions"); - setFinishTransactions(finishTransactions, result); - break; - case "setAllowSharingStoreAccount": - Boolean allowSharing = call.argument("allowSharing"); - setAllowSharingAppStoreAccount(allowSharing, result); - break; - case "getOfferings": - getOfferings(result); - break; - case "getProductInfo": - ArrayList productIdentifiers = call.argument("productIdentifiers"); - String type = call.argument("type"); - getProductInfo(productIdentifiers, type, result); - break; - case "purchaseProduct": - String productIdentifier = call.argument("productIdentifier"); - String oldSKU = call.argument("oldSKU"); - Integer prorationMode = call.argument("prorationMode"); - type = call.argument("type"); - purchaseProduct(productIdentifier, oldSKU, prorationMode, type, result); - break; - case "purchasePackage": - String packageIdentifier = call.argument("packageIdentifier"); - String offeringIdentifier = call.argument("offeringIdentifier"); - oldSKU = call.argument("oldSKU"); - prorationMode = call.argument("prorationMode"); - purchasePackage(packageIdentifier, offeringIdentifier, oldSKU, prorationMode, result); - break; - case "getAppUserID": - getAppUserID(result); - break; - case "restorePurchases": - restorePurchases(result); - break; - case "logIn": - String appUserIDToLogIn = call.argument("appUserID"); - logIn(appUserIDToLogIn, result); - break; - case "logOut": - logOut(result); - break; - case "setDebugLogsEnabled": - boolean enabled = call.argument("enabled") != null && (boolean) call.argument("enabled"); - setDebugLogsEnabled(enabled, result); - break; - case "setLogLevel": - String level = (String) call.argument("level"); - setLogLevel(level, result); - break; - case "setProxyURLString": - String proxyURLString = call.argument("proxyURLString"); - setProxyURLString(proxyURLString, result); - break; - case "getCustomerInfo": - getCustomerInfo(result); - break; - case "syncPurchases": - syncPurchases(result); - break; - case "setAutomaticAppleSearchAdsAttributionCollection": - // NOOP - result.success(null); - break; - case "enableAdServicesAttributionTokenCollection": - // NOOP - result.success(null); - break; - case "isAnonymous": - isAnonymous(result); - break; - case "isConfigured": - isConfigured(result); - break; - case "checkTrialOrIntroductoryPriceEligibility": - productIdentifiers = call.argument("productIdentifiers"); - checkTrialOrIntroductoryPriceEligibility(productIdentifiers, result); - break; - case "invalidateCustomerInfoCache": - invalidateCustomerInfoCache(result); - break; - case "getPromotionalOffer": - case "presentCodeRedemptionSheet": - case "setSimulatesAskToBuyInSandbox": - case "beginRefundRequestForActiveEntitlement": - case "beginRefundRequestForProduct": - case "beginRefundRequestForEntitlement": - // NOOP - result.success(null); - break; - case "setAttributes": - Map attributes = call.argument("attributes"); - setAttributes(attributes, result); - break; - case "setEmail": - String email = call.argument("email"); - setEmail(email, result); - break; - case "setPhoneNumber": - String phoneNumber = call.argument("phoneNumber"); - setPhoneNumber(phoneNumber, result); - break; - case "setDisplayName": - String displayName = call.argument("displayName"); - setDisplayName(displayName, result); - break; - case "setPushToken": - String pushToken = call.argument("pushToken"); - setPushToken(pushToken, result); - break; - case "setAdjustID": - String adjustID = call.argument("adjustID"); - setAdjustID(adjustID, result); - break; - case "setAppsflyerID": - String appsflyerID = call.argument("appsflyerID"); - setAppsflyerID(appsflyerID, result); - break; - case "setFBAnonymousID": - String fbAnonymousID = call.argument("fbAnonymousID"); - setFBAnonymousID(fbAnonymousID, result); - break; - case "setMparticleID": - String mparticleID = call.argument("mparticleID"); - setMparticleID(mparticleID, result); - break; - case "setCleverTapID": - String cleverTapID = call.argument("cleverTapID"); - setCleverTapID(cleverTapID, result); - break; - case "setMixpanelDistinctID": - String mixpanelDistinctID = call.argument("mixpanelDistinctID"); - setMixpanelDistinctID(mixpanelDistinctID, result); - break; - case "setFirebaseAppInstanceID": - String firebaseAppInstanceID = call.argument("firebaseAppInstanceID"); - setFirebaseAppInstanceID(firebaseAppInstanceID, result); - break; - case "setOnesignalID": - String onesignalID = call.argument("onesignalID"); - setOnesignalID(onesignalID, result); - break; - case "setAirshipChannelID": - String airshipChannelID = call.argument("airshipChannelID"); - setAirshipChannelID(airshipChannelID, result); - break; - case "setMediaSource": - String mediaSource = call.argument("mediaSource"); - setMediaSource(mediaSource, result); - break; - case "setCampaign": - String campaign = call.argument("campaign"); - setCampaign(campaign, result); - break; - case "setAdGroup": - String adGroup = call.argument("adGroup"); - setAdGroup(adGroup, result); - break; - case "setAd": - String ad = call.argument("ad"); - setAd(ad, result); - break; - case "setKeyword": - String keyword = call.argument("keyword"); - setKeyword(keyword, result); - break; - case "setCreative": - String creative = call.argument("creative"); - setCreative(creative, result); - break; - case "collectDeviceIdentifiers": - collectDeviceIdentifiers(result); - break; - case "canMakePayments": - List features = call.argument("features"); - canMakePayments(features, result); - break; - case "close": - close(result); - break; - case "setLogHandler": - setLogHandler(result); - break; - case "syncObserverModeAmazonPurchase": - String productID = call.argument("productID"); - String receiptID = call.argument("receiptID"); - String amazonUserID = call.argument("amazonUserID"); - String isoCurrencyCode = call.argument("isoCurrencyCode"); - Double price = call.argument("price"); - syncObserverModeAmazonPurchase(productID, receiptID, amazonUserID, isoCurrencyCode, - price, result); - break; - default: - result.notImplemented(); - break; - } - } - - private void setupPurchases(String apiKey, String appUserID, @Nullable Boolean observerMode, - @Nullable Boolean useAmazon, final Result result) { - if (this.applicationContext != null) { - PlatformInfo platformInfo = new PlatformInfo(PLATFORM_NAME, PLUGIN_VERSION); - Store store = Store.PLAY_STORE; - if (useAmazon != null && useAmazon) { - store = Store.AMAZON; - } - CommonKt.configure(this.applicationContext, apiKey, appUserID, observerMode, - platformInfo, store); - setUpdatedCustomerInfoListener(); - result.success(null); - } else { - result.error( - String.valueOf(PurchasesErrorCode.UnknownError.getCode()), - "Purchases can't be setup. There is no Application context", - null); - } - } - - private void setUpdatedCustomerInfoListener() { - Purchases.getSharedInstance().setUpdatedCustomerInfoListener(customerInfo -> { - Map customerInfoMap = CustomerInfoMapperKt.map(customerInfo); - invokeChannelMethodOnUiThread(CUSTOMER_INFO_UPDATED, customerInfoMap); - }); - } - - private void setFinishTransactions(@Nullable Boolean finishTransactions, Result result) { - if (finishTransactions != null) { - CommonKt.setFinishTransactions(finishTransactions); - result.success(null); - } else { - result.error( - INVALID_ARGS_ERROR_CODE, - "Missing finishTransactions argument", - null); - } - } - - @SuppressWarnings("deprecation") - private void setAllowSharingAppStoreAccount(@Nullable Boolean allowSharingAppStoreAccount, Result result) { - if (allowSharingAppStoreAccount != null) { - CommonKt.setAllowSharingAppStoreAccount(allowSharingAppStoreAccount); - result.success(null); - } else { - result.error( - INVALID_ARGS_ERROR_CODE, - "Missing allowSharing argument", - null); - } - } - - private void getOfferings(final Result result) { - CommonKt.getOfferings(getOnResult(result)); - } - - private void getProductInfo(ArrayList productIDs, String type, final Result result) { - CommonKt.getProductInfo(productIDs, type, new OnResultList() { - @Override - public void onReceived(List> map) { - result.success(map); - } - - @Override - public void onError(ErrorContainer errorContainer) { - reject(errorContainer, result); - } - }); - } - - private void purchaseProduct(final String productIdentifier, final String oldSKU, - @Nullable final Integer prorationMode, final String type, - final Result result) { - CommonKt.purchaseProduct( - getActivity(), - productIdentifier, - oldSKU, - prorationMode, - type, - getOnResult(result) - ); - } - - private void purchasePackage(final String packageIdentifier, - final String offeringIdentifier, - @Nullable final String oldSKU, - @Nullable final Integer prorationMode, - final Result result) { - CommonKt.purchasePackage( - getActivity(), - packageIdentifier, - offeringIdentifier, - oldSKU, - prorationMode, - getOnResult(result) - ); - } - - - private void getAppUserID(final Result result) { - result.success(CommonKt.getAppUserID()); - } - - private void restorePurchases(final Result result) { - CommonKt.restorePurchases(getOnResult(result)); - } - - private void logOut(final Result result) { - CommonKt.logOut(getOnResult(result)); - } - - private void logIn(String appUserID, final Result result) { - CommonKt.logIn(appUserID, getOnResult(result)); - } - - private void setDebugLogsEnabled(boolean enabled, final Result result) { - CommonKt.setDebugLogsEnabled(enabled); - result.success(null); - } - - private void syncObserverModeAmazonPurchase(String productID, - String receiptID, - String amazonUserID, - String isoCurrencyCode, - Double price, - final Result result) { - Purchases.getSharedInstance().syncObserverModeAmazonPurchase(productID, receiptID, - amazonUserID, isoCurrencyCode, price); - result.success(null); - } - - private void setLogLevel(String level, final Result result) { - CommonKt.setLogLevel(level); - result.success(null); - } - - private void setProxyURLString(String proxyURLString, final Result result) { - CommonKt.setProxyURLString(proxyURLString); - result.success(null); - } - - private void getCustomerInfo(final Result result) { - CommonKt.getCustomerInfo(getOnResult(result)); - } - - private void syncPurchases(final Result result) { - CommonKt.syncPurchases(); - result.success(null); - } - - private void isAnonymous(final Result result) { - result.success(CommonKt.isAnonymous()); - } - - private void isConfigured(final Result result) { - result.success(Purchases.isConfigured()); - } - - private void checkTrialOrIntroductoryPriceEligibility(ArrayList productIDs, final Result result) { - result.success(CommonKt.checkTrialOrIntroductoryPriceEligibility(productIDs)); - } - - private void invalidateCustomerInfoCache(Result result) { - CommonKt.invalidateCustomerInfoCache(); - result.success(null); - } - - //================================================================================ - // Subscriber Attributes - //================================================================================ - - private void setAttributes(Map map, final Result result) { - SubscriberAttributesKt.setAttributes(map); - result.success(null); - } - - private void setEmail(String email, final Result result) { - SubscriberAttributesKt.setEmail(email); - result.success(null); - } - - private void setPhoneNumber(String phoneNumber, final Result result) { - SubscriberAttributesKt.setPhoneNumber(phoneNumber); - result.success(null); - } - - private void setDisplayName(String displayName, final Result result) { - SubscriberAttributesKt.setDisplayName(displayName); - result.success(null); - } - - private void setPushToken(String pushToken, final Result result) { - SubscriberAttributesKt.setPushToken(pushToken); - result.success(null); - } - - private void setAdjustID(String adjustID, final Result result) { - SubscriberAttributesKt.setAdjustID(adjustID); - result.success(null); - } - - private void setAppsflyerID(String appsflyerID, final Result result) { - SubscriberAttributesKt.setAppsflyerID(appsflyerID); - result.success(null); - } - - private void setFBAnonymousID(String fbAnonymousID, final Result result) { - SubscriberAttributesKt.setFBAnonymousID(fbAnonymousID); - result.success(null); - } - - private void setMparticleID(String mparticleID, final Result result) { - SubscriberAttributesKt.setMparticleID(mparticleID); - result.success(null); - } - - private void setCleverTapID(String cleverTapID, final Result result) { - SubscriberAttributesKt.setCleverTapID(cleverTapID); - result.success(null); - } - - private void setMixpanelDistinctID(String mixpanelDistinctID, final Result result) { - SubscriberAttributesKt.setMixpanelDistinctID(mixpanelDistinctID); - result.success(null); - } - - private void setFirebaseAppInstanceID(String firebaseAppInstanceId, final Result result) { - SubscriberAttributesKt.setFirebaseAppInstanceID(firebaseAppInstanceId); - result.success(null); - } - - private void setOnesignalID(String onesignalID, final Result result) { - SubscriberAttributesKt.setOnesignalID(onesignalID); - result.success(null); - } - - private void setAirshipChannelID(String airshipChannelID, final Result result) { - SubscriberAttributesKt.setAirshipChannelID(airshipChannelID); - result.success(null); - } - - private void setMediaSource(String mediaSource, final Result result) { - SubscriberAttributesKt.setMediaSource(mediaSource); - result.success(null); - } - - private void setCampaign(String campaign, final Result result) { - SubscriberAttributesKt.setCampaign(campaign); - result.success(null); - } - - private void setAdGroup(String adGroup, final Result result) { - SubscriberAttributesKt.setAdGroup(adGroup); - result.success(null); - } - - private void setAd(String ad, final Result result) { - SubscriberAttributesKt.setAd(ad); - result.success(null); - } - - private void setKeyword(String keyword, final Result result) { - SubscriberAttributesKt.setKeyword(keyword); - result.success(null); - } - - private void setCreative(String creative, final Result result) { - SubscriberAttributesKt.setCreative(creative); - result.success(null); - } - - private void collectDeviceIdentifiers(final Result result) { - SubscriberAttributesKt.collectDeviceIdentifiers(); - result.success(null); - } - - private void canMakePayments(List features, final Result result) { - CommonKt.canMakePayments(applicationContext, - features, - new OnResultAny() { - @Override - public void onReceived(Boolean received) { - result.success(received); - } - - @Override - public void onError(@Nullable ErrorContainer errorContainer) { - reject(errorContainer, result); - } - }); - } - - private void close(final Result result) { - try { - Purchases.getSharedInstance().close(); - } catch (UninitializedPropertyAccessException e) { - // there's no instance so all good - } - result.success(null); - } - - private void setLogHandler(final Result result) { - CommonKt.setLogHandler(logData -> { - invokeChannelMethodOnUiThread(LOG_HANDLER_EVENT, logData); - return null; - }); - result.success(null); - } - - private void runOnUiThread(Runnable runnable) { - handler.post(runnable); - } - - @NotNull - private OnResult getOnResult(final Result result) { - return new OnResult() { - @Override - public void onReceived(Map map) { - result.success(map); - } - - @Override - public void onError(ErrorContainer errorContainer) { - reject(errorContainer, result); - } - }; - } - - private void reject(ErrorContainer errorContainer, Result result) { - result.error(String.valueOf(errorContainer.getCode()), errorContainer.getMessage(), errorContainer.getInfo()); - } - - private void invokeChannelMethodOnUiThread(String method, Object argumentsMap) { - runOnUiThread(() -> { - if (channel != null) { - channel.invokeMethod(method, argumentsMap); - } - }); - } - -} diff --git a/android/src/main/kotlin/com/revenuecat/purchases_flutter/Constants.kt b/android/src/main/kotlin/com/revenuecat/purchases_flutter/Constants.kt new file mode 100644 index 000000000..8b74e31a5 --- /dev/null +++ b/android/src/main/kotlin/com/revenuecat/purchases_flutter/Constants.kt @@ -0,0 +1,112 @@ +package com.revenuecat.purchases_flutter + +internal object Constants { + + object MethodNameConstants{ + const val setupPurchases = "setupPurchases" + const val setFinishTransactions = "setFinishTransactions" + const val setAllowSharingStoreAccount = "setAllowSharingStoreAccount" + const val getOfferings = "getOfferings" + const val getProductInfo = "getProductInfo" + const val purchaseProduct = "purchaseProduct" + const val purchasePackage = "purchasePackage" + const val getAppUserID = "getAppUserID" + const val restorePurchases = "restorePurchases" + const val logIn = "logIn" + const val logOut = "logOut" + const val setDebugLogsEnabled = "setDebugLogsEnabled" + const val setLogLevel = "setLogLevel" + const val setProxyURLString = "setProxyURLString" + const val getCustomerInfo = "getCustomerInfo" + const val syncPurchases = "syncPurchases" + const val setAutomaticAppleSearchAdsAttributionCollection = "setAutomaticAppleSearchAdsAttributionCollection" + const val enableAdServicesAttributionTokenCollection = "enableAdServicesAttributionTokenCollection" + const val isAnonymous = "isAnonymous" + const val isConfigured = "isConfigured" + const val checkTrialOrIntroductoryPriceEligibility = "checkTrialOrIntroductoryPriceEligibility" + const val invalidateCustomerInfoCache = "invalidateCustomerInfoCache" + const val getPromotionalOffer = "getPromotionalOffer" + const val presentCodeRedemptionSheet = "presentCodeRedemptionSheet" + const val setSimulatesAskToBuyInSandbox = "setSimulatesAskToBuyInSandbox" + const val beginRefundRequestForActiveEntitlement = "beginRefundRequestForActiveEntitlement" + const val beginRefundRequestForProduct = "beginRefundRequestForProduct" + const val beginRefundRequestForEntitlement = "beginRefundRequestForEntitlement" + const val setAttributes = "setAttributes" + const val setEmail = "setEmail" + const val setPhoneNumber = "setPhoneNumber" + const val setDisplayName = "setDisplayName" + const val setPushToken = "setPushToken" + const val setAdjustID = "setAdjustID" + const val setAppsflyerID = "setAppsflyerID" + const val setFBAnonymousID = "setFBAnonymousID" + const val setMparticleID = "setMparticleID" + const val setCleverTapID = "setCleverTapID" + const val setMixpanelDistinctID = "setMixpanelDistinctID" + const val setFirebaseAppInstanceID = "setFirebaseAppInstanceID" + const val setOnesignalID = "setOnesignalID" + const val setAirshipChannelID = "setAirshipChannelID" + const val setMediaSource = "setMediaSource" + const val setCampaign = "setCampaign" + const val setAdGroup = "setAdGroup" + const val setAd = "setAd" + const val setKeyword = "setKeyword" + const val setCreative = "setCreative" + const val collectDeviceIdentifiers = "collectDeviceIdentifiers" + const val canMakePayments = "canMakePayments" + const val close = "close" + const val setLogHandler = "setLogHandler" + const val syncObserverModeAmazonPurchase = "syncObserverModeAmazonPurchase" + } + + object MethodArgsNameConstants { + const val apiKey = "apiKey"; + const val appUserId = "appUserId"; + const val observerMode = "observerMode"; + const val userDefaultsSuiteName = "userDefaultsSuiteName"; + const val useAmazon = "useAmazon"; + const val usesStoreKit2IfAvailable = "usesStoreKit2IfAvailable"; + const val finishTransactions = "finishTransactions"; + const val allowSharing = "allowSharing"; + const val productIdentifiers = "productIdentifiers"; + const val type = "type"; + const val productIdentifier = "productIdentifier"; + const val oldSKU = "oldSKU"; + const val prorationMode = "prorationMode"; + const val packageIdentifier = "packageIdentifier"; + const val offeringIdentifier = "offeringIdentifier"; + const val signedDiscountTimestamp = "signedDiscountTimestamp"; + const val appUserID = "appUserID"; + const val enabled = "enabled"; + const val level = "level"; + const val proxyURLString = "proxyURLString"; + const val attributes = "attributes"; + const val email = "email"; + const val phoneNumber = "phoneNumber"; + const val displayName = "displayName"; + const val pushToken = "pushToken"; + const val adjustID = "adjustID"; + const val appsflyerID = "appsflyerID"; + const val fbAnonymousID = "fbAnonymousID"; + const val mparticleID = "mparticleID"; + const val cleverTapID = "cleverTapID"; + const val mixpanelDistinctID = "mixpanelDistinctID"; + const val firebaseAppInstanceID = "firebaseAppInstanceID"; + const val onesignalID = "onesignalID"; + const val airshipChannelID = "airshipChannelID"; + const val mediaSource = "mediaSource"; + const val campaign = "campaign"; + const val adGroup = "adGroup"; + const val ad = "ad"; + const val keyword = "keyword"; + const val creative = "creative"; + const val features = "features"; + const val discountIdentifier = "discountIdentifier"; + const val entitlementIdentifier = "entitlementIdentifier"; + const val productID = "productID"; + const val receiptID = "receiptID"; + const val amazonUserID = "amazonUserID"; + const val isoCurrencyCode = "isoCurrencyCode"; + const val price = "price"; + const val callbackID = "callbackID"; + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.kt b/android/src/main/kotlin/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.kt new file mode 100644 index 000000000..7422b496e --- /dev/null +++ b/android/src/main/kotlin/com/revenuecat/purchases_flutter/PurchasesFlutterPlugin.kt @@ -0,0 +1,652 @@ +package com.revenuecat.purchases_flutter + +import android.app.Activity +import android.content.Context +import android.os.Handler +import android.os.Looper +import com.revenuecat.purchases.CustomerInfo +import com.revenuecat.purchases.Purchases +import com.revenuecat.purchases.PurchasesErrorCode +import com.revenuecat.purchases.Store +import com.revenuecat.purchases.common.PlatformInfo +import com.revenuecat.purchases.hybridcommon.* +import com.revenuecat.purchases.hybridcommon.mappers.map +import com.revenuecat.purchases.interfaces.UpdatedCustomerInfoListener +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.PluginRegistry.Registrar + +/** + * PurchasesFlutterPlugin + */ +class PurchasesFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { + private val INVALID_ARGS_ERROR_CODE = "invalidArgs" + + // Only set registrar for v1 embedder. + @SuppressWarnings("deprecation") + private var registrar: Registrar? = null + + // Only set activity for v2 embedder. Always access activity from getActivity() method. + private var applicationContext: Context? = null + private var channel: MethodChannel? = null + private var activity: Activity? = null + private val handler = Handler(Looper.getMainLooper()) + override fun onAttachedToEngine(binding: FlutterPluginBinding) { + onAttachedToEngine(binding.binaryMessenger, binding.applicationContext) + } + + private fun onAttachedToEngine(messenger: BinaryMessenger, applicationContext: Context) { + channel = MethodChannel(messenger, "purchases_flutter") + this.applicationContext = applicationContext + channel!!.setMethodCallHandler(this) + } + + override fun onDetachedFromEngine(binding: FlutterPluginBinding) { + if (channel != null) { + channel!!.setMethodCallHandler(null) + } + channel = null + applicationContext = null + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + activity = null + } + + override fun onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity() + } + + private fun getActivity(): Activity? { + return if (registrar != null) registrar!!.activity() else activity + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + Constants.MethodNameConstants.setupPurchases -> { + val apiKey = call.argument(Constants.MethodArgsNameConstants.apiKey) + val appUserId = call.argument(Constants.MethodArgsNameConstants.appUserId) + val observerMode = call.argument(Constants.MethodArgsNameConstants.observerMode) + val useAmazon = call.argument(Constants.MethodArgsNameConstants.useAmazon) + val userDefaultsSuiteName = + call.argument(Constants.MethodArgsNameConstants.userDefaultsSuiteName) // iOS-only, unused. + val usesStoreKit2IfAvailable = + call.argument(Constants.MethodArgsNameConstants.usesStoreKit2IfAvailable) // iOS-only, unused. + setupPurchases(apiKey, appUserId, observerMode, useAmazon, result) + } + Constants.MethodNameConstants.setFinishTransactions -> { + val finishTransactions = call.argument(Constants.MethodArgsNameConstants.finishTransactions) + setFinishTransactions(finishTransactions, result) + } + Constants.MethodNameConstants.setAllowSharingStoreAccount -> { + val allowSharing = call.argument(Constants.MethodArgsNameConstants.allowSharing) + setAllowSharingAppStoreAccount(allowSharing, result) + } + Constants.MethodNameConstants.getOfferings -> getOfferings(result) + Constants.MethodNameConstants.getProductInfo -> { + val productIdentifiers = call.argument>(Constants.MethodArgsNameConstants.productIdentifiers)!! + val type = call.argument(Constants.MethodArgsNameConstants.type) + getProductInfo(productIdentifiers, type, result) + } + Constants.MethodNameConstants.purchaseProduct -> { + val productIdentifier = call.argument(Constants.MethodArgsNameConstants.productIdentifier) + val oldSKU = call.argument(Constants.MethodArgsNameConstants.oldSKU) + val prorationMode = call.argument(Constants.MethodArgsNameConstants.prorationMode) + val type = call.argument(Constants.MethodArgsNameConstants.type) + purchaseProduct(productIdentifier, oldSKU, prorationMode, type!!, result) + } + Constants.MethodNameConstants.purchasePackage -> { + val packageIdentifier = call.argument(Constants.MethodArgsNameConstants.packageIdentifier) + val offeringIdentifier = call.argument(Constants.MethodArgsNameConstants.offeringIdentifier) + val oldSKU = call.argument(Constants.MethodArgsNameConstants.oldSKU) + val prorationMode = call.argument(Constants.MethodArgsNameConstants.prorationMode) + purchasePackage( + packageIdentifier, + offeringIdentifier, + oldSKU, + prorationMode, + result + ) + } + Constants.MethodNameConstants.getAppUserID -> getAppUserID(result) + Constants.MethodNameConstants.restorePurchases -> restorePurchases(result) + Constants.MethodNameConstants.logIn -> { + val appUserIDToLogIn = call.argument(Constants.MethodArgsNameConstants.appUserID) + logIn(appUserIDToLogIn, result) + } + Constants.MethodNameConstants.logOut -> logOut(result) + Constants.MethodNameConstants.setDebugLogsEnabled -> { + val enabled = + call.argument(Constants.MethodArgsNameConstants.enabled) != null && call.argument(Constants.MethodArgsNameConstants.enabled) as Boolean + setDebugLogsEnabled(enabled, result) + } + Constants.MethodNameConstants.setLogLevel -> { + val level = call.argument(Constants.MethodArgsNameConstants.level) as String? + setLogLevel(level, result) + } + Constants.MethodNameConstants.setProxyURLString -> { + val proxyURLString = call.argument(Constants.MethodArgsNameConstants.proxyURLString) + setProxyURLString(proxyURLString, result) + } + Constants.MethodNameConstants.getCustomerInfo -> getCustomerInfo(result) + Constants.MethodNameConstants.syncPurchases -> syncPurchases(result) + Constants.MethodNameConstants.setAutomaticAppleSearchAdsAttributionCollection -> // NOOP + result.success(null) + Constants.MethodNameConstants.enableAdServicesAttributionTokenCollection -> // NOOP + result.success(null) + Constants.MethodNameConstants.isAnonymous -> isAnonymous(result) + Constants.MethodNameConstants.isConfigured -> isConfigured(result) + Constants.MethodNameConstants.checkTrialOrIntroductoryPriceEligibility -> { + val productIdentifiers = call.argument>(Constants.MethodArgsNameConstants.productIdentifiers) + checkTrialOrIntroductoryPriceEligibility(productIdentifiers!!, result) + } + Constants.MethodNameConstants.invalidateCustomerInfoCache -> invalidateCustomerInfoCache(result) + Constants.MethodNameConstants.getPromotionalOffer, + Constants.MethodNameConstants.presentCodeRedemptionSheet, + Constants.MethodNameConstants.setSimulatesAskToBuyInSandbox, + Constants.MethodNameConstants.beginRefundRequestForActiveEntitlement, + Constants.MethodNameConstants.beginRefundRequestForProduct, + Constants.MethodNameConstants.beginRefundRequestForEntitlement -> // NOOP + result.success(null) + Constants.MethodNameConstants.setAttributes -> { + val attributes = call.argument>(Constants.MethodArgsNameConstants.attributes)!! + setAttributes(attributes, result) + } + Constants.MethodNameConstants.setEmail -> { + val email = call.argument(Constants.MethodArgsNameConstants.email) + setEmail(email, result) + } + Constants.MethodNameConstants.setPhoneNumber -> { + val phoneNumber = call.argument(Constants.MethodArgsNameConstants.phoneNumber) + setPhoneNumber(phoneNumber, result) + } + Constants.MethodNameConstants.setDisplayName -> { + val displayName = call.argument(Constants.MethodArgsNameConstants.displayName) + setDisplayName(displayName, result) + } + Constants.MethodNameConstants.setPushToken -> { + val pushToken = call.argument(Constants.MethodArgsNameConstants.pushToken) + setPushToken(pushToken, result) + } + Constants.MethodNameConstants.setAdjustID -> { + val adjustID = call.argument(Constants.MethodArgsNameConstants.adjustID) + setAdjustID(adjustID, result) + } + Constants.MethodNameConstants.setAppsflyerID -> { + val appsflyerID = call.argument(Constants.MethodArgsNameConstants.appsflyerID) + setAppsflyerID(appsflyerID, result) + } + Constants.MethodNameConstants.setFBAnonymousID -> { + val fbAnonymousID = call.argument(Constants.MethodArgsNameConstants.fbAnonymousID) + setFBAnonymousID(fbAnonymousID, result) + } + Constants.MethodNameConstants.setMparticleID -> { + val mparticleID = call.argument(Constants.MethodArgsNameConstants.mparticleID) + setMparticleID(mparticleID, result) + } + Constants.MethodNameConstants.setCleverTapID -> { + val cleverTapID = call.argument(Constants.MethodArgsNameConstants.cleverTapID) + setCleverTapID(cleverTapID, result) + } + Constants.MethodNameConstants.setMixpanelDistinctID -> { + val mixpanelDistinctID = call.argument(Constants.MethodArgsNameConstants.mixpanelDistinctID) + setMixpanelDistinctID(mixpanelDistinctID, result) + } + Constants.MethodNameConstants.setFirebaseAppInstanceID -> { + val firebaseAppInstanceID = call.argument(Constants.MethodArgsNameConstants.firebaseAppInstanceID) + setFirebaseAppInstanceID(firebaseAppInstanceID, result) + } + Constants.MethodNameConstants.setOnesignalID -> { + val onesignalID = call.argument(Constants.MethodArgsNameConstants.onesignalID) + setOnesignalID(onesignalID, result) + } + Constants.MethodNameConstants.setAirshipChannelID -> { + val airshipChannelID = call.argument(Constants.MethodArgsNameConstants.airshipChannelID) + setAirshipChannelID(airshipChannelID, result) + } + Constants.MethodNameConstants.setMediaSource -> { + val mediaSource = call.argument(Constants.MethodArgsNameConstants.mediaSource) + setMediaSource(mediaSource, result) + } + Constants.MethodNameConstants.setCampaign -> { + val campaign = call.argument(Constants.MethodArgsNameConstants.campaign) + setCampaign(campaign, result) + } + Constants.MethodNameConstants.setAdGroup -> { + val adGroup = call.argument(Constants.MethodArgsNameConstants.adGroup) + setAdGroup(adGroup, result) + } + Constants.MethodNameConstants.setAd -> { + val ad = call.argument(Constants.MethodArgsNameConstants.ad) + setAd(ad, result) + } + Constants.MethodNameConstants.setKeyword -> { + val keyword = call.argument(Constants.MethodArgsNameConstants.keyword) + setKeyword(keyword, result) + } + Constants.MethodNameConstants.setCreative -> { + val creative = call.argument(Constants.MethodArgsNameConstants.creative) + setCreative(creative, result) + } + Constants.MethodNameConstants.collectDeviceIdentifiers -> collectDeviceIdentifiers(result) + Constants.MethodNameConstants.canMakePayments -> { + val features = call.argument>(Constants.MethodArgsNameConstants.features)!! + canMakePayments(features, result) + } + Constants.MethodNameConstants.close -> close(result) + Constants.MethodNameConstants.setLogHandler -> setLogHandler(result) + Constants.MethodNameConstants.syncObserverModeAmazonPurchase -> { + val productID = call.argument(Constants.MethodArgsNameConstants.productID) + val receiptID = call.argument(Constants.MethodArgsNameConstants.receiptID) + val amazonUserID = call.argument(Constants.MethodArgsNameConstants.amazonUserID) + val isoCurrencyCode = call.argument(Constants.MethodArgsNameConstants.isoCurrencyCode) + val price = call.argument(Constants.MethodArgsNameConstants.price) + syncObserverModeAmazonPurchase( + productID, receiptID, amazonUserID, isoCurrencyCode, + price, result + ) + } + else -> result.notImplemented() + } + } + + private fun setupPurchases( + apiKey: String?, appUserID: String?, observerMode: Boolean?, + useAmazon: Boolean?, result: MethodChannel.Result + ) { + if (applicationContext != null) { + val platformInfo = PlatformInfo(PLATFORM_NAME, PLUGIN_VERSION) + var store = Store.PLAY_STORE + if (useAmazon != null && useAmazon) { + store = Store.AMAZON + } + configure( + applicationContext!!, apiKey!!, appUserID, observerMode, + platformInfo, store + ) + setUpdatedCustomerInfoListener() + result.success(null) + } else { + result.error( + PurchasesErrorCode.UnknownError.code.toString(), + "Purchases can't be setup. There is no Application context", + null + ) + } + } + + private fun setUpdatedCustomerInfoListener() { + Purchases.sharedInstance.updatedCustomerInfoListener = + UpdatedCustomerInfoListener { customerInfo: CustomerInfo -> + val customerInfoMap = customerInfo.map() + invokeChannelMethodOnUiThread(CUSTOMER_INFO_UPDATED, customerInfoMap) + } + } + + private fun setFinishTransactions(finishTransactions: Boolean?, result: MethodChannel.Result) { + if (finishTransactions != null) { + setFinishTransactions(finishTransactions) + result.success(null) + } else { + result.error( + INVALID_ARGS_ERROR_CODE, + "Missing finishTransactions argument", + null + ) + } + } + + @SuppressWarnings("deprecation") + private fun setAllowSharingAppStoreAccount( + allowSharingAppStoreAccount: Boolean?, + result: MethodChannel.Result + ) { + if (allowSharingAppStoreAccount != null) { + setAllowSharingAppStoreAccount(allowSharingAppStoreAccount) + result.success(null) + } else { + result.error( + INVALID_ARGS_ERROR_CODE, + "Missing allowSharing argument", + null + ) + } + } + + private fun getOfferings(result: MethodChannel.Result) { + getOfferings(getOnResult(result)) + } + + private fun getProductInfo( + productIDs: ArrayList, + type: String?, + result: MethodChannel.Result + ) { + getProductInfo(productIDs, type!!, object : OnResultList { + override fun onReceived(map: List?>?) { + result.success(map) + } + + override fun onError(errorContainer: ErrorContainer) { + reject(errorContainer, result) + } + }) + } + + private fun purchaseProduct( + productIdentifier: String?, oldSKU: String?, + prorationMode: Int?, type: String, + result: MethodChannel.Result + ) { + purchaseProduct( + getActivity(), + productIdentifier!!, + oldSKU, + prorationMode, + type, + getOnResult(result) + ) + } + + private fun purchasePackage( + packageIdentifier: String?, + offeringIdentifier: String?, + oldSKU: String?, + prorationMode: Int?, + result: MethodChannel.Result + ) { + purchasePackage( + getActivity(), + packageIdentifier!!, + offeringIdentifier!!, + oldSKU, + prorationMode, + getOnResult(result) + ) + } + + private fun getAppUserID(result: MethodChannel.Result) { + result.success(getAppUserID()) + } + + private fun restorePurchases(result: MethodChannel.Result) { + restorePurchases(getOnResult(result)) + } + + private fun logOut(result: MethodChannel.Result) { + logOut(getOnResult(result)) + } + + private fun logIn(appUserID: String?, result: MethodChannel.Result) { + logIn(appUserID!!, getOnResult(result)) + } + + private fun setDebugLogsEnabled(enabled: Boolean, result: MethodChannel.Result) { + setDebugLogsEnabled(enabled) + result.success(null) + } + + private fun syncObserverModeAmazonPurchase( + productID: String?, + receiptID: String?, + amazonUserID: String?, + isoCurrencyCode: String?, + price: Double?, + result: MethodChannel.Result + ) { + Purchases.sharedInstance.syncObserverModeAmazonPurchase( + productID ?: "", receiptID ?: "", + amazonUserID ?: "", isoCurrencyCode, price + ) + result.success(null) + } + + private fun setLogLevel(level: String?, result: MethodChannel.Result) { + setLogLevel(level!!) + result.success(null) + } + + private fun setProxyURLString(proxyURLString: String?, result: MethodChannel.Result) { + setProxyURLString(proxyURLString) + result.success(null) + } + + private fun getCustomerInfo(result: MethodChannel.Result) { + getCustomerInfo(getOnResult(result)) + } + + private fun syncPurchases(result: MethodChannel.Result) { + syncPurchases() + result.success(null) + } + + private fun isAnonymous(result: MethodChannel.Result) { + result.success(isAnonymous()) + } + + private fun isConfigured(result: MethodChannel.Result) { + result.success(Purchases.isConfigured) + } + + private fun checkTrialOrIntroductoryPriceEligibility( + productIDs: ArrayList, + result: MethodChannel.Result + ) { + result.success(checkTrialOrIntroductoryPriceEligibility(productIDs)) + } + + private fun invalidateCustomerInfoCache(result: MethodChannel.Result) { + invalidateCustomerInfoCache() + result.success(null) + } + + //================================================================================ + // Subscriber Attributes + //================================================================================ + private fun setAttributes(map: Map, result: MethodChannel.Result) { + setAttributes(map) + result.success(null) + } + + private fun setEmail(email: String?, result: MethodChannel.Result) { + setEmail(email) + result.success(null) + } + + private fun setPhoneNumber(phoneNumber: String?, result: MethodChannel.Result) { + setPhoneNumber(phoneNumber) + result.success(null) + } + + private fun setDisplayName(displayName: String?, result: MethodChannel.Result) { + setDisplayName(displayName) + result.success(null) + } + + private fun setPushToken(pushToken: String?, result: MethodChannel.Result) { + setPushToken(pushToken) + result.success(null) + } + + private fun setAdjustID(adjustID: String?, result: MethodChannel.Result) { + setAdjustID(adjustID) + result.success(null) + } + + private fun setAppsflyerID(appsflyerID: String?, result: MethodChannel.Result) { + setAppsflyerID(appsflyerID) + result.success(null) + } + + private fun setFBAnonymousID(fbAnonymousID: String?, result: MethodChannel.Result) { + setFBAnonymousID(fbAnonymousID) + result.success(null) + } + + private fun setMparticleID(mparticleID: String?, result: MethodChannel.Result) { + setMparticleID(mparticleID) + result.success(null) + } + + private fun setCleverTapID(cleverTapID: String?, result: MethodChannel.Result) { + setCleverTapID(cleverTapID) + result.success(null) + } + + private fun setMixpanelDistinctID(mixpanelDistinctID: String?, result: MethodChannel.Result) { + setMixpanelDistinctID(mixpanelDistinctID) + result.success(null) + } + + private fun setFirebaseAppInstanceID( + firebaseAppInstanceId: String?, + result: MethodChannel.Result + ) { + setFirebaseAppInstanceID(firebaseAppInstanceId) + result.success(null) + } + + private fun setOnesignalID(onesignalID: String?, result: MethodChannel.Result) { + setOnesignalID(onesignalID) + result.success(null) + } + + private fun setAirshipChannelID(airshipChannelID: String?, result: MethodChannel.Result) { + setAirshipChannelID(airshipChannelID) + result.success(null) + } + + private fun setMediaSource(mediaSource: String?, result: MethodChannel.Result) { + setMediaSource(mediaSource) + result.success(null) + } + + private fun setCampaign(campaign: String?, result: MethodChannel.Result) { + setCampaign(campaign) + result.success(null) + } + + private fun setAdGroup(adGroup: String?, result: MethodChannel.Result) { + setAdGroup(adGroup) + result.success(null) + } + + private fun setAd(ad: String?, result: MethodChannel.Result) { + setAd(ad) + result.success(null) + } + + private fun setKeyword(keyword: String?, result: MethodChannel.Result) { + setKeyword(keyword) + result.success(null) + } + + private fun setCreative(creative: String?, result: MethodChannel.Result) { + setCreative(creative) + result.success(null) + } + + private fun collectDeviceIdentifiers(result: MethodChannel.Result) { + collectDeviceIdentifiers() + result.success(null) + } + + private fun canMakePayments(features: List, resultMethodChannel: MethodChannel.Result) { + canMakePayments( + applicationContext!!, + features, + object : OnResultAny { + override fun onError(errorContainer: ErrorContainer?) { + reject(errorContainer, resultMethodChannel) + } + + override fun onReceived(result: Boolean) { + resultMethodChannel.success(result) + } + }) + } + + private fun close(result: MethodChannel.Result) { + try { + Purchases.sharedInstance.close() + } catch (e: UninitializedPropertyAccessException) { + // there's no instance so all good + } + result.success(null) + } + + private fun setLogHandler(result: MethodChannel.Result) { + setLogHandler {logDetails -> + invokeChannelMethodOnUiThread(LOG_HANDLER_EVENT, logDetails) + } + result.success(null) + } + + private fun runOnUiThread(runnable: Runnable) { + handler.post(runnable) + } + + private fun getOnResult(result: MethodChannel.Result): OnResult { + return object : OnResult { + override fun onReceived(map: MutableMap) { + result.success(map) + } + + override fun onError(errorContainer: ErrorContainer) { + reject(errorContainer, result) + } + } + } + + private fun reject(errorContainer: ErrorContainer?, result: MethodChannel.Result) { + result.error(errorContainer!!.code.toString(), errorContainer.message, errorContainer.info) + } + + private fun invokeChannelMethodOnUiThread(method: String, argumentsMap: Any) { + runOnUiThread { + if (channel != null) { + channel!!.invokeMethod(method, argumentsMap) + } + } + } + + companion object { + private const val CUSTOMER_INFO_UPDATED = "Purchases-CustomerInfoUpdated" + protected const val LOG_HANDLER_EVENT = "Purchases-LogHandlerEvent" + private const val PLATFORM_NAME = "flutter" + private const val PLUGIN_VERSION = "4.13.0-SNAPSHOT" + + /** + * Plugin registration. + */ + @Suppress("DEPRECATION", "unused") + @JvmStatic + fun registerWith(registrar: Registrar) { + val instance = PurchasesFlutterPlugin() + instance.onAttachedToEngine(registrar.messenger(), registrar.context()) + instance.registrar = registrar + registrar.addViewDestroyListener { + try { + Purchases.sharedInstance.close() + } catch (e: UninitializedPropertyAccessException) { + // there's no instance so all good + } + false + } + } + } +} \ No newline at end of file