diff --git a/.github/badges/branches.svg b/.github/badges/branches.svg
index 9a401d14..097c3a25 100644
--- a/.github/badges/branches.svg
+++ b/.github/badges/branches.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
index 8790d333..13424a65 100644
--- a/.github/badges/jacoco.svg
+++ b/.github/badges/jacoco.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f13bf307..31e29860 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,14 @@
The changelog for `Superwall`. Also see the [releases](https://github.com/superwall/Superwall-Android/releases) on GitHub.
+## 2.7.14
+
+## Fixes
+
+- Fixes test mode products not being loaded properly in PW
+- Improve exponential backoff retry for Play Service unavailable
+- Fix Custom Info date serialization issues
+
## 2.7.13
## Fixes
diff --git a/superwall/src/main/java/com/superwall/sdk/billing/BillingClientUseCase.kt b/superwall/src/main/java/com/superwall/sdk/billing/BillingClientUseCase.kt
index 374026da..4e04f3d6 100644
--- a/superwall/src/main/java/com/superwall/sdk/billing/BillingClientUseCase.kt
+++ b/superwall/src/main/java/com/superwall/sdk/billing/BillingClientUseCase.kt
@@ -14,6 +14,8 @@ internal typealias ExecuteRequestOnUIThreadFunction = (delayInMillis: Long, onEr
private const val MAX_RETRIES_DEFAULT = 3
private const val RETRY_TIMER_START_MILLISECONDS = 878L // So it gets close to 15 minutes in last retry
internal const val RETRY_TIMER_MAX_TIME_MILLISECONDS = 1000L * 60L * 15L // 15 minutes
+private const val FOREGROUND_BACKOFF_START_MILLISECONDS = 250L
+private const val FOREGROUND_BACKOFF_MAX_ATTEMPTS = 2
internal interface UseCaseParams {
val appInBackground: Boolean
@@ -33,6 +35,7 @@ internal abstract class BillingClientUseCase(
private val maxRetries: Int = MAX_RETRIES_DEFAULT
private var retryAttempt: Int = 0
private var retryBackoffMilliseconds = RETRY_TIMER_START_MILLISECONDS
+ private var foregroundBackoffAttempt: Int = 0
fun run(delayMilliseconds: Long = 0) {
executeRequestOnUIThread(delayMilliseconds) { connectionError ->
@@ -144,11 +147,28 @@ internal abstract class BillingClientUseCase(
} else {
onError(billingResult)
}
+ } else if (retryAttempt < maxRetries) {
+ Logger.debug(
+ logLevel = LogLevel.warn,
+ scope = LogScope.productsManager,
+ message = "Billing unavailable in foreground. Retry ${retryAttempt + 1}/$maxRetries (immediate).",
+ )
+ retryAttempt++
+ executeAsync()
+ } else if (foregroundBackoffAttempt < FOREGROUND_BACKOFF_MAX_ATTEMPTS) {
+ val delay = FOREGROUND_BACKOFF_START_MILLISECONDS shl foregroundBackoffAttempt
+ Logger.debug(
+ logLevel = LogLevel.warn,
+ scope = LogScope.productsManager,
+ message = "Billing unavailable in foreground. Backoff retry ${foregroundBackoffAttempt + 1}/$FOREGROUND_BACKOFF_MAX_ATTEMPTS in ${delay}ms.",
+ )
+ foregroundBackoffAttempt++
+ run(delay)
} else {
Logger.debug(
logLevel = LogLevel.error,
scope = LogScope.productsManager,
- message = "Billing is unavailable. App is in foreground. Won't retry.",
+ message = "Billing unavailable. Foreground retries exhausted.",
)
onError(billingResult)
}
diff --git a/superwall/src/main/java/com/superwall/sdk/models/customer/CustomerInfo.kt b/superwall/src/main/java/com/superwall/sdk/models/customer/CustomerInfo.kt
index 044317f1..bce0c4e5 100644
--- a/superwall/src/main/java/com/superwall/sdk/models/customer/CustomerInfo.kt
+++ b/superwall/src/main/java/com/superwall/sdk/models/customer/CustomerInfo.kt
@@ -3,6 +3,7 @@ package com.superwall.sdk.models.customer
import com.superwall.sdk.models.entitlements.Entitlement
import com.superwall.sdk.models.entitlements.SubscriptionStatus
import com.superwall.sdk.models.product.Store
+import com.superwall.sdk.network.JsonFactory
import com.superwall.sdk.storage.LatestDeviceCustomerInfo
import com.superwall.sdk.storage.LatestRedemptionResponse
import com.superwall.sdk.storage.Storage
@@ -10,8 +11,6 @@ import com.superwall.sdk.storage.core_data.convertFromJsonElement
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonNamingStrategy
import kotlinx.serialization.json.jsonObject
/**
@@ -58,7 +57,7 @@ data class CustomerInfo(
*/
fun toParams(): Map {
if (isPlaceholder) return emptyMap()
- val obj = paramsJson.encodeToJsonElement(serializer(), this).jsonObject
+ val obj = JsonFactory.JSON.encodeToJsonElement(serializer(), this).jsonObject
return obj
.mapValues { (_, value) -> value.convertFromJsonElement() }
}
@@ -87,16 +86,6 @@ data class CustomerInfo(
}
companion object {
- private val paramsJson = Json {
- try {
-
- namingStrategy = JsonNamingStrategy.SnakeCase
- } catch (e: Throwable) {
- }
- encodeDefaults = true;
- ignoreUnknownKeys = true
- }
-
/**
* Creates a blank CustomerInfo instance for testing or default states.
*/
diff --git a/superwall/src/main/java/com/superwall/sdk/store/StoreManager.kt b/superwall/src/main/java/com/superwall/sdk/store/StoreManager.kt
index f8c0b852..3367d15a 100644
--- a/superwall/src/main/java/com/superwall/sdk/store/StoreManager.kt
+++ b/superwall/src/main/java/com/superwall/sdk/store/StoreManager.kt
@@ -133,11 +133,21 @@ class StoreManager(
}
private suspend fun fetchOrAwaitProducts(fullProductIds: Set): Map {
+ val testProducts = testMode?.takeIf { it.isTestMode }?.testProductsByFullId.orEmpty()
+ val testHits: Map =
+ if (testProducts.isEmpty()) {
+ emptyMap()
+ } else {
+ fullProductIds.mapNotNull { id -> testProducts[id]?.let { id to it } }.toMap()
+ }
+ val remainingIds = fullProductIds - testHits.keys
+ if (remainingIds.isEmpty()) return testHits
+
val cached = mutableMapOf()
val loading = mutableListOf>()
val newDeferreds = mutableMapOf>()
- for (id in fullProductIds) {
+ for (id in remainingIds) {
val state =
productsByFullId.getOrPut(id) {
val deferred = CompletableDeferred()
@@ -181,7 +191,7 @@ class StoreManager(
val fetched = fetchNewProducts(newDeferreds)
- return cached + awaited + fetched
+ return testHits + cached + awaited + fetched
}
private suspend fun fetchNewProducts(deferreds: Map>): Map {
diff --git a/version.env b/version.env
index f92112cc..bf505a00 100644
--- a/version.env
+++ b/version.env
@@ -1 +1 @@
-SUPERWALL_VERSION=2.7.13
+SUPERWALL_VERSION=2.7.14