Repository: RevenueCat/purchases-android
Pull request #2260: Allows configuring `showInAppMessagesAutomatically` and `pendingTransactionsForPrepaidPlansEnabled` in customEntitlementComputation mode
Author: login: JayShortway
Labels: name: pr:other
name: pr:force_patch
description: Forces a patch release
Created: 2025-03-17T10:52:07Z | Merged: 2025-03-17T15:03:30Z

## Description
Allows for more configuration in customEntitlementComputation mode. Supersedes https://github.com/RevenueCat/purchases-android/pull/2243. 


Diff:
diff --git a/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/java/PurchasesAPI.java b/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/java/PurchasesAPI.java
index b24c94f4a3..268a2d2d2a 100644
--- a/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/java/PurchasesAPI.java
+++ b/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/java/PurchasesAPI.java
@@ -3,6 +3,7 @@
 import android.content.Context;
 
 import com.revenuecat.purchases.Purchases;
+import com.revenuecat.purchases.PurchasesConfigurationForCustomEntitlementsComputationMode;
 
 import java.util.concurrent.ExecutorService;
 
@@ -12,6 +13,15 @@ final class PurchasesAPI {
     static void checkConfiguration(final Context context,
                                    final ExecutorService executorService) {
         Purchases.configureInCustomEntitlementsComputationMode(context, "", "");
+        Purchases.configureInCustomEntitlementsComputationMode(
+                new PurchasesConfigurationForCustomEntitlementsComputationMode.Builder(
+                        context,
+                        "",
+                        ""
+                ).showInAppMessagesAutomatically(false)
+                        .pendingTransactionsForPrepaidPlansEnabled(false)
+                        .build()
+        );
     }
 
     static void check(final Purchases purchases) {
diff --git a/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/kotlin/PurchasesAPI.kt b/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/kotlin/PurchasesAPI.kt
index 4bddff228e..70afde057c 100644
--- a/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/kotlin/PurchasesAPI.kt
+++ b/api-tester/src/customEntitlementComputation/kotlin/com/revenuecat/apitester/kotlin/PurchasesAPI.kt
@@ -2,6 +2,7 @@ package com.revenuecat.apitester.kotlin
 
 import android.content.Context
 import com.revenuecat.purchases.Purchases
+import com.revenuecat.purchases.PurchasesConfigurationForCustomEntitlementsComputationMode
 
 @Suppress("unused", "UNUSED_VARIABLE", "EmptyFunctionBlock")
 private class PurchasesAPI {
@@ -11,6 +12,17 @@ private class PurchasesAPI {
         val configured: Boolean = Purchases.isConfigured
 
         Purchases.configureInCustomEntitlementsComputationMode(context, apiKey = "", appUserID = "")
+        Purchases.configureInCustomEntitlementsComputationMode(
+            PurchasesConfigurationForCustomEntitlementsComputationMode
+                .Builder(
+                    context = context,
+                    apiKey = "",
+                    appUserID = "",
+                )
+                .showInAppMessagesAutomatically(false)
+                .pendingTransactionsForPrepaidPlansEnabled(false)
+                .build(),
+        )
     }
 
     fun check(purchases: Purchases) {
diff --git a/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt b/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt
index dec4b902b4..21a0a13f01 100644
--- a/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt
+++ b/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt
@@ -285,19 +285,46 @@ class Purchases internal constructor(
             context: Context,
             apiKey: String,
             appUserID: String,
+        ): Purchases =
+            configureInCustomEntitlementsComputationMode(
+                configuration = PurchasesConfigurationForCustomEntitlementsComputationMode
+                    .Builder(
+                        context = context,
+                        apiKey = apiKey,
+                        appUserID = appUserID,
+                    )
+                    .showInAppMessagesAutomatically(true)
+                    .pendingTransactionsForPrepaidPlansEnabled(true)
+                    .build(),
+            )
+
+        /**
+         * Configures an instance of the Purchases SDK with a specified API key. The instance will
+         * be set as a singleton. You should access the singleton instance using [Purchases.sharedInstance]
+         * @param configuration The [PurchasesConfigurationForCustomEntitlementsComputationMode] object you wish to use
+         * to configure [Purchases].
+         *
+         * @return An instantiated `[Purchases] object that has been set as a singleton.
+         */
+        @JvmStatic
+        fun configureInCustomEntitlementsComputationMode(
+            configuration: PurchasesConfigurationForCustomEntitlementsComputationMode,
         ): Purchases {
             if (isConfigured) {
                 infoLog(ConfigureStrings.INSTANCE_ALREADY_EXISTS)
             }
-            val configuration = PurchasesConfiguration.Builder(context, apiKey)
-                .appUserID(appUserID)
-                .dangerousSettings(DangerousSettings(customEntitlementComputation = true))
-                .pendingTransactionsForPrepaidPlansEnabled(true)
-                .build()
+            val purchasesConfiguration = with(configuration) {
+                PurchasesConfiguration.Builder(context, apiKey)
+                    .appUserID(appUserID)
+                    .dangerousSettings(DangerousSettings(customEntitlementComputation = true))
+                    .showInAppMessagesAutomatically(showInAppMessagesAutomatically)
+                    .pendingTransactionsForPrepaidPlansEnabled(pendingTransactionsForPrepaidPlansEnabled)
+                    .build()
+            }
             return PurchasesFactory(
-                isDebugBuild = DefaultIsDebugBuildProvider(context),
+                isDebugBuild = DefaultIsDebugBuildProvider(configuration.context),
             ).createPurchases(
-                configuration,
+                purchasesConfiguration,
                 platformInfo,
                 proxyURL,
             ).also {
diff --git a/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/PurchasesConfigurationForCustomEntitlementsComputationMode.kt b/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/PurchasesConfigurationForCustomEntitlementsComputationMode.kt
new file mode 100644
index 0000000000..e2462507a1
--- /dev/null
+++ b/purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/PurchasesConfigurationForCustomEntitlementsComputationMode.kt
@@ -0,0 +1,62 @@
+package com.revenuecat.purchases
+
+import android.content.Context
+import com.revenuecat.purchases.PurchasesConfigurationForCustomEntitlementsComputationMode.Builder
+
+/**
+ * Holds parameters to initialize the SDK in Custom Entitlements Computation mode. Create an instance of this class
+ * using the [Builder] and pass it to [Purchases.configureInCustomEntitlementsComputationMode].
+ */
+class PurchasesConfigurationForCustomEntitlementsComputationMode internal constructor(
+    internal val context: Context,
+    internal val apiKey: String,
+    internal val appUserID: String,
+    internal val showInAppMessagesAutomatically: Boolean,
+    internal val pendingTransactionsForPrepaidPlansEnabled: Boolean,
+) {
+    /**
+     * @param context: the Application context object of your Application.
+     * @param apiKey: the API Key for your app. Obtained from the RevenueCat dashboard.
+     * @param appUserID: a unique id for identifying the user.
+     */
+    class Builder(
+        private val context: Context,
+        private val apiKey: String,
+        private val appUserID: String,
+    ) {
+        private var showInAppMessagesAutomatically: Boolean = true
+        private var pendingTransactionsForPrepaidPlansEnabled: Boolean = true
+
+        /**
+         * Enable this setting to show in-app messages from Google Play automatically. Default is enabled.
+         * For more info: [rev.cat](https://rev.cat/googleplayinappmessaging).
+         *
+         * If this setting is disabled, you can show the snackbar by calling [Purchases.showInAppMessagesIfNeeded].
+         */
+        fun showInAppMessagesAutomatically(enabled: Boolean): Builder {
+            showInAppMessagesAutomatically = enabled
+            return this
+        }
+
+        /**
+         * Enable this setting if you want to allow pending purchases for prepaid subscriptions (only supported in
+         * Google Play). Note that entitlements are not granted until payment is done. Default is enabled.
+         */
+        fun pendingTransactionsForPrepaidPlansEnabled(enabled: Boolean): Builder {
+            pendingTransactionsForPrepaidPlansEnabled = enabled
+            return this
+        }
+
+        /**
+         * Creates a [PurchasesConfigurationForCustomEntitlementsComputationMode] instance with the specified
+         * properties.
+         */
+        fun build() = PurchasesConfigurationForCustomEntitlementsComputationMode(
+            context = context,
+            apiKey = apiKey,
+            appUserID = appUserID,
+            showInAppMessagesAutomatically = showInAppMessagesAutomatically,
+            pendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled,
+        )
+    }
+}


Reviews:
user: login: joshdholtz
body: One small questions but this looks good to ship! :shipit: 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T12:10:53Z
user: login: JayShortway
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T12:13:38Z
user: login: joshdholtz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T12:13:52Z
user: login: vegaro
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T12:25:30Z
user: login: vegaro
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T14:55:27Z
user: login: MarkVillacampa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T14:55:31Z
user: login: JayShortway
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T14:58:18Z
user: login: MarkVillacampa
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T14:59:32Z

Review comments:
diff_hunk: @@ -278,21 +278,32 @@ class Purchases internal constructor(
          * @param context: the Application context object of your Application.
          * @param apiKey: the API Key for your app. Obtained from the RevenueCat dashboard.
          * @param appUserID: a unique id for identifying the user.
+         * @param showInAppMessagesAutomatically Enable this setting to show in-app messages from Google Play
+         * automatically. Default is enabled. For more info: [rev.cat](https://rev.cat/googleplayinappmessaging). If
+         * this setting is disabled, you can show the snackbar by calling [Purchases.showInAppMessagesIfNeeded].
+         * @param pendingTransactionsForPrepaidPlansEnabled Enable this setting if you want to allow pending purchases
+         * for prepaid subscriptions (only supported in Google Play). Note that entitlements are not granted until
+         * payment is done. Default is enabled.
+         *
          * @return An instantiated `[Purchases] object that has been set as a singleton.
          */
+        @JvmOverloads
         @JvmStatic
         fun configureInCustomEntitlementsComputationMode(
             context: Context,
             apiKey: String,
             appUserID: String,
+            showInAppMessagesAutomatically: Boolean = true,
+            pendingTransactionsForPrepaidPlansEnabled: Boolean = true,
         ): Purchases {
             if (isConfigured) {
                 infoLog(ConfigureStrings.INSTANCE_ALREADY_EXISTS)
             }
             val configuration = PurchasesConfiguration.Builder(context, apiKey)
                 .appUserID(appUserID)
                 .dangerousSettings(DangerousSettings(customEntitlementComputation = true))
-                .pendingTransactionsForPrepaidPlansEnabled(true)
+                .showInAppMessagesAutomatically(showInAppMessagesAutomatically)
path: purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt
user: login: joshdholtz
body: Was `.showInAppMessagesAutomatically` being defaulted to `true` inside of the `PurchasesConfiguration.Builder`? 

Just asking since this line is new 😇 
created_at: 2025-03-17T12:10:37Z
author_association: MEMBER
diff_hunk: @@ -278,21 +278,32 @@ class Purchases internal constructor(
          * @param context: the Application context object of your Application.
          * @param apiKey: the API Key for your app. Obtained from the RevenueCat dashboard.
          * @param appUserID: a unique id for identifying the user.
+         * @param showInAppMessagesAutomatically Enable this setting to show in-app messages from Google Play
+         * automatically. Default is enabled. For more info: [rev.cat](https://rev.cat/googleplayinappmessaging). If
+         * this setting is disabled, you can show the snackbar by calling [Purchases.showInAppMessagesIfNeeded].
+         * @param pendingTransactionsForPrepaidPlansEnabled Enable this setting if you want to allow pending purchases
+         * for prepaid subscriptions (only supported in Google Play). Note that entitlements are not granted until
+         * payment is done. Default is enabled.
+         *
          * @return An instantiated `[Purchases] object that has been set as a singleton.
          */
+        @JvmOverloads
         @JvmStatic
         fun configureInCustomEntitlementsComputationMode(
             context: Context,
             apiKey: String,
             appUserID: String,
+            showInAppMessagesAutomatically: Boolean = true,
+            pendingTransactionsForPrepaidPlansEnabled: Boolean = true,
         ): Purchases {
             if (isConfigured) {
                 infoLog(ConfigureStrings.INSTANCE_ALREADY_EXISTS)
             }
             val configuration = PurchasesConfiguration.Builder(context, apiKey)
                 .appUserID(appUserID)
                 .dangerousSettings(DangerousSettings(customEntitlementComputation = true))
-                .pendingTransactionsForPrepaidPlansEnabled(true)
+                .showInAppMessagesAutomatically(showInAppMessagesAutomatically)
path: purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt
user: login: JayShortway
body: Correct!
created_at: 2025-03-17T12:13:38Z
author_association: MEMBER
diff_hunk: @@ -278,21 +278,32 @@ class Purchases internal constructor(
          * @param context: the Application context object of your Application.
          * @param apiKey: the API Key for your app. Obtained from the RevenueCat dashboard.
          * @param appUserID: a unique id for identifying the user.
+         * @param showInAppMessagesAutomatically Enable this setting to show in-app messages from Google Play
+         * automatically. Default is enabled. For more info: [rev.cat](https://rev.cat/googleplayinappmessaging). If
+         * this setting is disabled, you can show the snackbar by calling [Purchases.showInAppMessagesIfNeeded].
+         * @param pendingTransactionsForPrepaidPlansEnabled Enable this setting if you want to allow pending purchases
+         * for prepaid subscriptions (only supported in Google Play). Note that entitlements are not granted until
+         * payment is done. Default is enabled.
+         *
          * @return An instantiated `[Purchases] object that has been set as a singleton.
          */
+        @JvmOverloads
         @JvmStatic
         fun configureInCustomEntitlementsComputationMode(
             context: Context,
             apiKey: String,
             appUserID: String,
+            showInAppMessagesAutomatically: Boolean = true,
+            pendingTransactionsForPrepaidPlansEnabled: Boolean = true,
         ): Purchases {
             if (isConfigured) {
                 infoLog(ConfigureStrings.INSTANCE_ALREADY_EXISTS)
             }
             val configuration = PurchasesConfiguration.Builder(context, apiKey)
                 .appUserID(appUserID)
                 .dangerousSettings(DangerousSettings(customEntitlementComputation = true))
-                .pendingTransactionsForPrepaidPlansEnabled(true)
+                .showInAppMessagesAutomatically(showInAppMessagesAutomatically)
path: purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/Purchases.kt
user: login: joshdholtz
body: SHIP IT
created_at: 2025-03-17T12:13:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,62 @@
+package com.revenuecat.purchases
+
+import android.content.Context
+import com.revenuecat.purchases.PurchasesConfigurationForCustomEntitlementsComputationMode.Builder
+
+/**
+ * Holds parameters to initialize the SDK in Custom Entitlements Computation mode. Create an instance of this class
+ * using the [Builder] and pass it to [Purchases.configureInCustomEntitlementsComputationMode].
+ */
+class PurchasesConfigurationForCustomEntitlementsComputationMode internal constructor(
+    internal val context: Context,
+    internal val apiKey: String,
+    internal val appUserID: String,
+    internal val showInAppMessagesAutomatically: Boolean,
+    internal val pendingTransactionsForPrepaidPlansEnabled: Boolean,
+) {
+    /**
+     * @param context: the Application context object of your Application.
+     * @param apiKey: the API Key for your app. Obtained from the RevenueCat dashboard.
+     * @param appUserID: a unique id for identifying the user.
+     */
+    class Builder(
+        private val context: Context,
+        private val apiKey: String,
+        private val appUserID: String,
path: purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/PurchasesConfigurationForCustomEntitlementsComputationMode.kt
user: login: MarkVillacampa
body: I think `appUserID` is not part of the regular `PurchasesConfiguration.Builder` initialiser, did you put it here for any reason? (not a big deal, just want to want to make sure it's intentional) 
created_at: 2025-03-17T14:55:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,62 @@
+package com.revenuecat.purchases
+
+import android.content.Context
+import com.revenuecat.purchases.PurchasesConfigurationForCustomEntitlementsComputationMode.Builder
+
+/**
+ * Holds parameters to initialize the SDK in Custom Entitlements Computation mode. Create an instance of this class
+ * using the [Builder] and pass it to [Purchases.configureInCustomEntitlementsComputationMode].
+ */
+class PurchasesConfigurationForCustomEntitlementsComputationMode internal constructor(
+    internal val context: Context,
+    internal val apiKey: String,
+    internal val appUserID: String,
+    internal val showInAppMessagesAutomatically: Boolean,
+    internal val pendingTransactionsForPrepaidPlansEnabled: Boolean,
+) {
+    /**
+     * @param context: the Application context object of your Application.
+     * @param apiKey: the API Key for your app. Obtained from the RevenueCat dashboard.
+     * @param appUserID: a unique id for identifying the user.
+     */
+    class Builder(
+        private val context: Context,
+        private val apiKey: String,
+        private val appUserID: String,
path: purchases/src/customEntitlementComputation/kotlin/com/revenuecat/purchases/PurchasesConfigurationForCustomEntitlementsComputationMode.kt
user: login: JayShortway
body: Yea, as the parameter is required in the original `configureInCustomEntitlementsComputationMode()` method, I made it required in the `Configuration` variant too. 
created_at: 2025-03-17T14:58:18Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-03-17T11:18:37Z
body: ## [Codecov](https://app.codecov.io/gh/RevenueCat/purchases-android/pull/2260?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RevenueCat) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 80.28%. Comparing base [(`0ad7e52`)](https://app.codecov.io/gh/RevenueCat/purchases-android/commit/0ad7e522e5e56da1c8f3c3a901a0266ef246b34c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RevenueCat) to head [(`8b41bb3`)](https://app.codecov.io/gh/RevenueCat/purchases-android/commit/8b41bb303e5ba03fde1512688bf2d0eb52e31aed?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RevenueCat).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #2260   +/-   ##
=======================================
  Coverage   80.28%   80.28%           
=======================================
  Files         278      278           
  Lines        9484     9484           
  Branches     1336     1336           
=======================================
  Hits         7614     7614           
  Misses       1310     1310           
  Partials      560      560           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/RevenueCat/purchases-android/pull/2260?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RevenueCat).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RevenueCat).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: emerge-tools[bot]
created_at: 2025-03-17T13:30:17Z
body: # 📸 Snapshot Test 
## 6 modified, 258 unchanged
| Name | Added | Removed | Modified | Renamed | Unchanged | Errored | Approval |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">TestPurchasesUIAndroidCompatibility</a><br>com.revenuecat.testpurchasesuiandroidcompatibility | <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?tab=added&utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">0</a> | <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?tab=removed&utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">0</a> | <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?tab=changed&utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">6</a> | <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?tab=renamed&utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">0</a> | <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?tab=unchanged&utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">258</a> | <a href="https://www.emergetools.com/snapshot/compare/2026a642-80bc-4a87-9e81-d78f73d3ef8e?tab=errors&utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">0</a> | ✅ Approved |


---

:flying_saucer: Powered by <a href="https://www.emergetools.com/?utm_source=github&utm_medium=vcs" target="_blank" rel="noreferrer noopener">Emerge Tools</a>
author_association: NONE