Skip to content

Commit d0a873b

Browse files
NickSxticlaude
andcommitted
refactor: adapter pattern for entitlementsUpdateListener (review feedback)
- Create EntitlementsUpdateListenerAdapter that wraps QEntitlementsUpdateListener as QDeferredPurchasesListener, extracting entitlements from purchaseResult - Change deferredPurchaseCompleted signature to include QPurchaseResult (a deferred purchase is a purchase, so the callback provides both transaction details and the purchase result with entitlements) - Remove entitlementsUpdateListener from InternalConfig - only deferredPurchasesListener remains - Delete unused EntitlementsUpdateListenerProvider interface - Deprecated setters now wrap legacy listener via adapter - Both deferred call sites in QProductCenterManager use single deferredPurchasesListener invocation (no more dual listener logic) - Update tests for adapter behavior and removed properties Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d5d431 commit d0a873b

9 files changed

Lines changed: 170 additions & 106 deletions

File tree

sdk/src/main/java/com/qonversion/android/sdk/QonversionConfig.kt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.qonversion.android.sdk.dto.QLaunchMode
77
import android.content.Context
88
import androidx.annotation.RawRes
99
import com.qonversion.android.sdk.dto.entitlements.QEntitlementsCacheLifetime
10+
import com.qonversion.android.sdk.internal.EntitlementsUpdateListenerAdapter
1011
import com.qonversion.android.sdk.internal.dto.config.CacheConfig
1112
import com.qonversion.android.sdk.internal.dto.config.PrimaryConfig
1213
import com.qonversion.android.sdk.internal.application
@@ -27,7 +28,8 @@ class QonversionConfig internal constructor(
2728
internal val application: Application,
2829
internal val primaryConfig: PrimaryConfig,
2930
internal val cacheConfig: CacheConfig,
30-
internal val entitlementsUpdateListener: QEntitlementsUpdateListener?,
31+
// Review feedback (Task 6): removed entitlementsUpdateListener from config.
32+
// The deprecated builder method now wraps it in an adapter stored as deferredPurchasesListener.
3133
internal val deferredPurchasesListener: QDeferredPurchasesListener? = null
3234
) {
3335

@@ -49,7 +51,6 @@ class QonversionConfig internal constructor(
4951
) {
5052
internal var environment = QEnvironment.Production
5153
internal var entitlementsCacheLifetime = QEntitlementsCacheLifetime.Month
52-
internal var entitlementsUpdateListener: QEntitlementsUpdateListener? = null
5354
internal var deferredPurchasesListener: QDeferredPurchasesListener? = null
5455
internal var proxyUrl: String? = null
5556
internal var isKidsMode: Boolean = false
@@ -99,16 +100,17 @@ class QonversionConfig internal constructor(
99100
/**
100101
* Provide a listener to be notified about asynchronous user entitlements updates.
101102
*
102-
* Make sure you provide this listener for being up-to-date with the user entitlements.
103-
* Else you can lose some important updates. Also, please, consider that this listener
104-
* should live for the whole lifetime of the application.
103+
* @deprecated Use [setDeferredPurchasesListener] instead. It provides full transaction
104+
* details including product ID, transaction ID, and value.
105105
*
106106
* @param entitlementsUpdateListener listener to be called when entitlements update.
107107
* @return builder instance for chain calls.
108108
*/
109109
@Deprecated("Use setDeferredPurchasesListener instead", ReplaceWith("setDeferredPurchasesListener(listener)"))
110110
fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener): Builder = apply {
111-
this.entitlementsUpdateListener = entitlementsUpdateListener
111+
// Adapter pattern: wrap the legacy listener so the SDK internally works
112+
// with a single listener type (QDeferredPurchasesListener).
113+
this.deferredPurchasesListener = EntitlementsUpdateListenerAdapter(entitlementsUpdateListener)
112114
}
113115

114116
/**
@@ -147,7 +149,7 @@ class QonversionConfig internal constructor(
147149

148150
/**
149151
* Use this function to enable Qonversion SDK Kids mode.
150-
* With this mode activated, our SDK does not collect any information that violates Google Childrens Privacy Policy.
152+
* With this mode activated, our SDK does not collect any information that violates Google Children's Privacy Policy.
151153
*/
152154
fun enableKidsMode(): Builder = apply {
153155
this.isKidsMode = true
@@ -186,7 +188,6 @@ class QonversionConfig internal constructor(
186188
context.application,
187189
primaryConfig,
188190
cacheConfig,
189-
entitlementsUpdateListener,
190191
deferredPurchasesListener
191192
)
192193
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.qonversion.android.sdk.internal
2+
3+
import com.qonversion.android.sdk.dto.QDeferredTransaction
4+
import com.qonversion.android.sdk.dto.QPurchaseResult
5+
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
6+
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
7+
8+
/**
9+
* Adapter that wraps a deprecated [QEntitlementsUpdateListener] as a [QDeferredPurchasesListener].
10+
*
11+
* When the deprecated setEntitlementsUpdateListener() is called, this adapter allows
12+
* QProductCenterManager to work with a single listener type internally
13+
* (QDeferredPurchasesListener only), eliminating duplicate invocation logic.
14+
*
15+
* The adapter extracts entitlements from the [QPurchaseResult] and forwards them
16+
* to the wrapped legacy listener via onEntitlementsUpdated().
17+
*/
18+
internal class EntitlementsUpdateListenerAdapter(
19+
private val legacyListener: QEntitlementsUpdateListener
20+
) : QDeferredPurchasesListener {
21+
22+
override fun deferredPurchaseCompleted(
23+
transaction: QDeferredTransaction,
24+
purchaseResult: QPurchaseResult
25+
) {
26+
legacyListener.onEntitlementsUpdated(purchaseResult.entitlements)
27+
}
28+
}

sdk/src/main/java/com/qonversion/android/sdk/internal/InternalConfig.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@ import com.qonversion.android.sdk.dto.QEnvironment
66
import com.qonversion.android.sdk.dto.QLaunchMode
77
import com.qonversion.android.sdk.internal.dto.config.CacheConfig
88
import com.qonversion.android.sdk.internal.provider.CacheConfigProvider
9-
import com.qonversion.android.sdk.internal.provider.EntitlementsUpdateListenerProvider
10-
119
import com.qonversion.android.sdk.internal.provider.EnvironmentProvider
1210
import com.qonversion.android.sdk.internal.provider.PrimaryConfigProvider
1311
import com.qonversion.android.sdk.internal.provider.UidProvider
14-
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
1512
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
1613

14+
// Review feedback (Task 6): removed entitlementsUpdateListener property.
15+
// InternalConfig now only holds deferredPurchasesListener. When the deprecated
16+
// setEntitlementsUpdateListener() is called, the legacy listener is wrapped in
17+
// EntitlementsUpdateListenerAdapter and stored as deferredPurchasesListener.
18+
// This eliminates duplicate listener storage and dual invocation logic.
1719
internal class InternalConfig(
1820
override var primaryConfig: PrimaryConfig,
1921
override val cacheConfig: CacheConfig,
20-
override var entitlementsUpdateListener: QEntitlementsUpdateListener?,
2122
var deferredPurchasesListener: QDeferredPurchasesListener? = null
2223
) : EnvironmentProvider,
2324
PrimaryConfigProvider,
2425
CacheConfigProvider,
25-
UidProvider,
26-
EntitlementsUpdateListenerProvider {
26+
UidProvider {
2727

2828
@Volatile
2929
var fatalError: HttpError? = null
@@ -38,7 +38,6 @@ internal class InternalConfig(
3838
constructor(qonversionConfig: QonversionConfig) : this(
3939
qonversionConfig.primaryConfig,
4040
qonversionConfig.cacheConfig,
41-
qonversionConfig.entitlementsUpdateListener,
4241
qonversionConfig.deferredPurchasesListener
4342
)
4443

sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,10 @@ internal class QProductCenterManager internal constructor(
468468
callback.onSuccess(user)
469469
}
470470

471+
// Review feedback (Task 6): deprecated setter wraps legacy listener in adapter.
472+
// QProductCenterManager only works with deferredPurchasesListener internally.
471473
fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener) {
472-
internalConfig.entitlementsUpdateListener = entitlementsUpdateListener
474+
internalConfig.deferredPurchasesListener = EntitlementsUpdateListenerAdapter(entitlementsUpdateListener)
473475
}
474476

475477
fun setDeferredPurchasesListener(listener: QDeferredPurchasesListener) {
@@ -570,12 +572,12 @@ internal class QProductCenterManager internal constructor(
570572
)
571573

572574
val entitlements = permissions.toEntitlementsMap()
575+
val purchaseResult = QPurchaseResult.successFromFallback(entitlements, purchase)
573576
if (callback != null) {
574-
callback.onResult(QPurchaseResult.successFromFallback(entitlements, purchase))
577+
callback.onResult(purchaseResult)
575578
} else {
576-
// Deferred purchase fallback - notify both listeners
577-
@Suppress("DEPRECATION")
578-
internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated(entitlements)
579+
// Review feedback (Task 6): single listener invocation.
580+
// The adapter handles forwarding entitlements to legacy listeners.
579581
val deferredTransaction = QDeferredTransaction(
580582
productId = purchase.productId,
581583
transactionId = purchase.purchaseToken,
@@ -584,7 +586,7 @@ internal class QProductCenterManager internal constructor(
584586
value = 0.0,
585587
currency = null
586588
)
587-
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(deferredTransaction)
589+
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(deferredTransaction, purchaseResult)
588590
}
589591
}
590592

@@ -1079,20 +1081,19 @@ internal class QProductCenterManager internal constructor(
10791081

10801082
removePurchaseOptions(product?.storeId)
10811083

1084+
// Review feedback (Task 6): single listener invocation.
1085+
// The adapter handles forwarding entitlements to legacy listeners.
10821086
if (purchaseCallback == null) {
1083-
// Notify deprecated EntitlementsUpdateListener (backward compat)
1084-
@Suppress("DEPRECATION")
1085-
internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated(entitlements)
1086-
// Notify new DeferredPurchasesListener with full transaction details
10871087
val deferredTransaction = QDeferredTransaction(
10881088
productId = purchase.productId,
10891089
transactionId = purchase.purchaseToken,
10901090
originalTransactionId = purchase.orderId,
10911091
type = QDeferredTransactionType.Unknown,
1092-
value = 0.0, // price not available from Purchase object
1092+
value = 0.0,
10931093
currency = null
10941094
)
1095-
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(deferredTransaction)
1095+
val purchaseResult = QPurchaseResult.success(entitlements, purchase)
1096+
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(deferredTransaction, purchaseResult)
10961097
} else {
10971098
purchaseCallback.onResult(QPurchaseResult.success(entitlements, purchase))
10981099
}

sdk/src/main/java/com/qonversion/android/sdk/internal/provider/EntitlementsUpdateListenerProvider.kt

Lines changed: 0 additions & 8 deletions
This file was deleted.

sdk/src/main/java/com/qonversion/android/sdk/listeners/QDeferredPurchasesListener.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package com.qonversion.android.sdk.listeners
22

33
import com.qonversion.android.sdk.dto.QDeferredTransaction
4+
import com.qonversion.android.sdk.dto.QPurchaseResult
45

56
/**
67
* The listener for deferred purchase completions.
78
*
89
* Deferred purchases happen when transactions require additional steps to complete,
910
* such as pending transactions on Google Play or purchases requiring additional access grants.
10-
* This listener provides full transaction details, including for consumable products
11-
* without associated entitlements.
11+
* This listener provides full transaction details and the purchase result with entitlements.
1212
*
1313
* It can be provided to the [QonversionConfig](com.qonversion.android.sdk.QonversionConfig)
1414
* via [QonversionConfig.Builder.setDeferredPurchasesListener](com.qonversion.android.sdk.QonversionConfig.Builder.setDeferredPurchasesListener)
@@ -20,8 +20,12 @@ interface QDeferredPurchasesListener {
2020
/**
2121
* Called when a deferred purchase completes.
2222
*
23+
* A deferred purchase is fundamentally a purchase, so the callback provides both
24+
* the transaction details and the full purchase result containing entitlements.
25+
*
2326
* @param transaction the completed deferred transaction with full details
2427
* including product ID, transaction ID, type, value, and currency.
28+
* @param purchaseResult the purchase result containing entitlements granted by this purchase.
2529
*/
26-
fun deferredPurchaseCompleted(transaction: QDeferredTransaction)
30+
fun deferredPurchaseCompleted(transaction: QDeferredTransaction, purchaseResult: QPurchaseResult)
2731
}

sdk/src/test/java/com/qonversion/android/sdk/QonversionConfigTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.qonversion.android.sdk.internal.application
1010
import com.qonversion.android.sdk.internal.dto.config.CacheConfig
1111
import com.qonversion.android.sdk.internal.dto.config.PrimaryConfig
1212
import com.qonversion.android.sdk.internal.isDebuggable
13-
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
13+
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
1414
import io.mockk.every
1515
import io.mockk.mockk
1616
import io.mockk.mockkStatic
@@ -32,7 +32,7 @@ internal class QonversionConfigTest {
3232
private val projectKey = "some project key"
3333
private val mockLaunchMode = mockk<QLaunchMode>()
3434
private val mockEnvironment = mockk<QEnvironment>()
35-
private val mockEntitlementsListener = mockk<QEntitlementsUpdateListener>()
35+
private val mockDeferredPurchasesListener = mockk<QDeferredPurchasesListener>()
3636
private val mockEntitlementsCacheLifetime = mockk<QEntitlementsCacheLifetime>()
3737
private val mockProxyUrl = "mock url"
3838
private val mockPrimaryConfig = PrimaryConfig(projectKey, mockLaunchMode, mockEnvironment, mockProxyUrl)
@@ -53,14 +53,14 @@ internal class QonversionConfigTest {
5353
mockApplication,
5454
mockPrimaryConfig,
5555
mockCacheConfig,
56-
mockEntitlementsListener
56+
mockDeferredPurchasesListener
5757
)
5858

5959
// then
6060
assertThat(config.application).isSameAs(mockApplication)
6161
assertThat(config.primaryConfig).isSameAs(mockPrimaryConfig)
6262
assertThat(config.cacheConfig).isSameAs(mockCacheConfig)
63-
assertThat(config.entitlementsUpdateListener).isSameAs(mockEntitlementsListener)
63+
assertThat(config.deferredPurchasesListener).isSameAs(mockDeferredPurchasesListener)
6464
}
6565
}
6666

@@ -164,14 +164,14 @@ internal class QonversionConfigTest {
164164
.apply {
165165
environment = mockEnvironment
166166
entitlementsCacheLifetime = mockEntitlementsCacheLifetime
167-
entitlementsUpdateListener = mockEntitlementsListener
167+
deferredPurchasesListener = mockDeferredPurchasesListener
168168
proxyUrl = mockProxyUrl
169169
}
170170
val expResult = QonversionConfig(
171171
mockApplication,
172172
mockPrimaryConfig,
173173
mockCacheConfig,
174-
mockEntitlementsListener
174+
mockDeferredPurchasesListener
175175
)
176176

177177
// when

0 commit comments

Comments
 (0)