Skip to content

Commit d9da704

Browse files
committed
Revert "refactor: match iOS coexistence model for deferred purchases listener"
This reverts commit 4cf269d.
1 parent 4cf269d commit d9da704

File tree

7 files changed

+75
-73
lines changed

7 files changed

+75
-73
lines changed

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

Lines changed: 4 additions & 4 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,6 @@ class QonversionConfig internal constructor(
2728
internal val application: Application,
2829
internal val primaryConfig: PrimaryConfig,
2930
internal val cacheConfig: CacheConfig,
30-
internal val entitlementsUpdateListener: QEntitlementsUpdateListener? = null,
3131
internal val deferredPurchasesListener: QDeferredPurchasesListener? = null
3232
) {
3333

@@ -49,7 +49,6 @@ class QonversionConfig internal constructor(
4949
) {
5050
internal var environment = QEnvironment.Production
5151
internal var entitlementsCacheLifetime = QEntitlementsCacheLifetime.Month
52-
internal var entitlementsUpdateListener: QEntitlementsUpdateListener? = null
5352
internal var deferredPurchasesListener: QDeferredPurchasesListener? = null
5453
internal var proxyUrl: String? = null
5554
internal var isKidsMode: Boolean = false
@@ -107,7 +106,9 @@ class QonversionConfig internal constructor(
107106
*/
108107
@Deprecated("Use setDeferredPurchasesListener instead", ReplaceWith("setDeferredPurchasesListener(listener)"))
109108
fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener): Builder = apply {
110-
this.entitlementsUpdateListener = entitlementsUpdateListener
109+
// Adapter pattern: wrap the legacy listener so the SDK internally works
110+
// with a single listener type (QDeferredPurchasesListener).
111+
this.deferredPurchasesListener = EntitlementsUpdateListenerAdapter(entitlementsUpdateListener)
111112
}
112113

113114
/**
@@ -185,7 +186,6 @@ class QonversionConfig internal constructor(
185186
context.application,
186187
primaryConfig,
187188
cacheConfig,
188-
entitlementsUpdateListener,
189189
deferredPurchasesListener
190190
)
191191
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.qonversion.android.sdk.internal
2+
3+
import com.qonversion.android.sdk.dto.QPurchaseResult
4+
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
5+
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
6+
7+
/**
8+
* Adapter that wraps a deprecated [QEntitlementsUpdateListener] as a [QDeferredPurchasesListener].
9+
*
10+
* When the deprecated setEntitlementsUpdateListener() is called, this adapter allows
11+
* QProductCenterManager to work with a single listener type internally
12+
* (QDeferredPurchasesListener only), eliminating duplicate invocation logic.
13+
*
14+
* The adapter extracts entitlements from the [QPurchaseResult] and forwards them
15+
* to the wrapped legacy listener via onEntitlementsUpdated().
16+
*/
17+
internal class EntitlementsUpdateListenerAdapter(
18+
private val legacyListener: QEntitlementsUpdateListener
19+
) : QDeferredPurchasesListener {
20+
21+
override fun deferredPurchaseCompleted(purchaseResult: QPurchaseResult) {
22+
legacyListener.onEntitlementsUpdated(purchaseResult.entitlements)
23+
}
24+
}

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@ import com.qonversion.android.sdk.internal.provider.EnvironmentProvider
1010
import com.qonversion.android.sdk.internal.provider.PrimaryConfigProvider
1111
import com.qonversion.android.sdk.internal.provider.UidProvider
1212
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
13-
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
1413

1514
internal class InternalConfig(
1615
override var primaryConfig: PrimaryConfig,
1716
override val cacheConfig: CacheConfig,
18-
var entitlementsUpdateListener: QEntitlementsUpdateListener? = null,
1917
var deferredPurchasesListener: QDeferredPurchasesListener? = null
2018
) : EnvironmentProvider,
2119
PrimaryConfigProvider,
@@ -35,7 +33,6 @@ internal class InternalConfig(
3533
constructor(qonversionConfig: QonversionConfig) : this(
3634
qonversionConfig.primaryConfig,
3735
qonversionConfig.cacheConfig,
38-
qonversionConfig.entitlementsUpdateListener,
3936
qonversionConfig.deferredPurchasesListener
4037
)
4138

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ internal class QProductCenterManager internal constructor(
467467
}
468468

469469
fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener) {
470-
internalConfig.entitlementsUpdateListener = entitlementsUpdateListener
470+
internalConfig.deferredPurchasesListener = EntitlementsUpdateListenerAdapter(entitlementsUpdateListener)
471471
}
472472

473473
fun setDeferredPurchasesListener(listener: QDeferredPurchasesListener) {
@@ -572,9 +572,6 @@ internal class QProductCenterManager internal constructor(
572572
if (callback != null) {
573573
callback.onResult(purchaseResult)
574574
} else {
575-
// Both listeners fire independently (matching iOS behavior)
576-
@Suppress("DEPRECATION")
577-
internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated(entitlements)
578575
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(purchaseResult)
579576
}
580577
}
@@ -1071,9 +1068,6 @@ internal class QProductCenterManager internal constructor(
10711068
removePurchaseOptions(product?.storeId)
10721069

10731070
if (purchaseCallback == null) {
1074-
// Both listeners fire independently (matching iOS behavior)
1075-
@Suppress("DEPRECATION")
1076-
internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated(entitlements)
10771071
val purchaseResult = QPurchaseResult.success(entitlements, purchase)
10781072
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(purchaseResult)
10791073
} else {

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ 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
1313
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
14-
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
1514
import io.mockk.every
1615
import io.mockk.mockk
1716
import io.mockk.mockkStatic
@@ -33,7 +32,6 @@ internal class QonversionConfigTest {
3332
private val projectKey = "some project key"
3433
private val mockLaunchMode = mockk<QLaunchMode>()
3534
private val mockEnvironment = mockk<QEnvironment>()
36-
private val mockEntitlementsUpdateListener = mockk<QEntitlementsUpdateListener>()
3735
private val mockDeferredPurchasesListener = mockk<QDeferredPurchasesListener>()
3836
private val mockEntitlementsCacheLifetime = mockk<QEntitlementsCacheLifetime>()
3937
private val mockProxyUrl = "mock url"
@@ -55,15 +53,13 @@ internal class QonversionConfigTest {
5553
mockApplication,
5654
mockPrimaryConfig,
5755
mockCacheConfig,
58-
mockEntitlementsUpdateListener,
5956
mockDeferredPurchasesListener
6057
)
6158

6259
// then
6360
assertThat(config.application).isSameAs(mockApplication)
6461
assertThat(config.primaryConfig).isSameAs(mockPrimaryConfig)
6562
assertThat(config.cacheConfig).isSameAs(mockCacheConfig)
66-
assertThat(config.entitlementsUpdateListener).isSameAs(mockEntitlementsUpdateListener)
6763
assertThat(config.deferredPurchasesListener).isSameAs(mockDeferredPurchasesListener)
6864
}
6965
}
@@ -168,15 +164,13 @@ internal class QonversionConfigTest {
168164
.apply {
169165
environment = mockEnvironment
170166
entitlementsCacheLifetime = mockEntitlementsCacheLifetime
171-
entitlementsUpdateListener = mockEntitlementsUpdateListener
172167
deferredPurchasesListener = mockDeferredPurchasesListener
173168
proxyUrl = mockProxyUrl
174169
}
175170
val expResult = QonversionConfig(
176171
mockApplication,
177172
mockPrimaryConfig,
178173
mockCacheConfig,
179-
mockEntitlementsUpdateListener,
180174
mockDeferredPurchasesListener
181175
)
182176

sdk/src/test/java/com/qonversion/android/sdk/internal/DeferredPurchasesListenerTest.kt

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,80 +17,81 @@ import org.robolectric.annotation.Config
1717
@Config(sdk = [Build.VERSION_CODES.O_MR1])
1818
internal class DeferredPurchasesListenerTest {
1919

20-
private lateinit var internalConfig: InternalConfig
20+
private val mockConfig = mockk<InternalConfig>(relaxed = true)
2121

2222
@Before
2323
fun setUp() {
2424
clearAllMocks()
25-
internalConfig = InternalConfig(
26-
mockk(relaxed = true),
27-
mockk(relaxed = true)
28-
)
2925
}
3026

31-
// -- Setter tests --
27+
// -- Adapter pattern tests --
3228

3329
@Test
34-
fun `setDeferredPurchasesListener stores listener`() {
35-
val listener = mockk<QDeferredPurchasesListener>()
30+
fun `adapter forwards entitlements from purchaseResult to legacy listener`() {
31+
// The adapter extracts entitlements from QPurchaseResult and passes them
32+
// to the wrapped QEntitlementsUpdateListener - this is the core adapter behavior.
33+
val legacyListener = mockk<QEntitlementsUpdateListener>()
34+
every { legacyListener.onEntitlementsUpdated(any()) } just Runs
3635

37-
internalConfig.deferredPurchasesListener = listener
36+
val adapter = EntitlementsUpdateListenerAdapter(legacyListener)
3837

39-
assertEquals(listener, internalConfig.deferredPurchasesListener)
40-
}
38+
val entitlements = mapOf("premium" to mockk<QEntitlement>())
39+
val purchaseResult = mockk<QPurchaseResult>()
40+
every { purchaseResult.entitlements } returns entitlements
4141

42-
@Test
43-
fun `setDeferredPurchasesListener to null clears listener`() {
44-
val listener = mockk<QDeferredPurchasesListener>()
45-
internalConfig.deferredPurchasesListener = listener
46-
47-
internalConfig.deferredPurchasesListener = null
42+
adapter.deferredPurchaseCompleted(purchaseResult)
4843

49-
assertNull(internalConfig.deferredPurchasesListener)
44+
verify(exactly = 1) { legacyListener.onEntitlementsUpdated(entitlements) }
5045
}
5146

5247
@Test
53-
fun `setEntitlementsUpdateListener stores listener`() {
54-
val listener = mockk<QEntitlementsUpdateListener>()
48+
fun `adapter forwards empty entitlements for consumable products`() {
49+
// Consumable products may have no entitlements - the adapter should still
50+
// forward the empty map to the legacy listener.
51+
val legacyListener = mockk<QEntitlementsUpdateListener>()
52+
every { legacyListener.onEntitlementsUpdated(any()) } just Runs
5553

56-
internalConfig.entitlementsUpdateListener = listener
54+
val adapter = EntitlementsUpdateListenerAdapter(legacyListener)
5755

58-
assertEquals(listener, internalConfig.entitlementsUpdateListener)
59-
}
56+
val purchaseResult = mockk<QPurchaseResult>()
57+
every { purchaseResult.entitlements } returns emptyMap()
6058

61-
// -- Coexistence tests (match iOS behavior) --
59+
adapter.deferredPurchaseCompleted(purchaseResult)
6260

63-
@Test
64-
fun `both listeners can be set independently`() {
65-
val deferredListener = mockk<QDeferredPurchasesListener>()
66-
val entitlementsListener = mockk<QEntitlementsUpdateListener>()
61+
verify(exactly = 1) { legacyListener.onEntitlementsUpdated(emptyMap()) }
62+
}
6763

68-
internalConfig.deferredPurchasesListener = deferredListener
69-
internalConfig.entitlementsUpdateListener = entitlementsListener
64+
@Test
65+
fun `adapter implements QDeferredPurchasesListener interface`() {
66+
// The adapter must be a valid QDeferredPurchasesListener so it can replace
67+
// the entitlementsUpdateListener everywhere internally.
68+
val legacyListener = mockk<QEntitlementsUpdateListener>()
69+
val adapter = EntitlementsUpdateListenerAdapter(legacyListener)
7070

71-
assertEquals(deferredListener, internalConfig.deferredPurchasesListener)
72-
assertEquals(entitlementsListener, internalConfig.entitlementsUpdateListener)
71+
assertTrue(adapter is QDeferredPurchasesListener)
7372
}
7473

74+
// -- Config tests --
75+
7576
@Test
76-
fun `setting deferred listener does not affect entitlements listener`() {
77-
val entitlementsListener = mockk<QEntitlementsUpdateListener>()
78-
internalConfig.entitlementsUpdateListener = entitlementsListener
77+
fun `setDeferredPurchasesListener stores listener in config`() {
78+
val listener = mockk<QDeferredPurchasesListener>()
79+
every { mockConfig.deferredPurchasesListener = any() } just Runs
80+
every { mockConfig.deferredPurchasesListener } returns listener
7981

80-
val deferredListener = mockk<QDeferredPurchasesListener>()
81-
internalConfig.deferredPurchasesListener = deferredListener
82+
mockConfig.deferredPurchasesListener = listener
8283

83-
assertEquals(entitlementsListener, internalConfig.entitlementsUpdateListener)
84+
assertEquals(listener, mockConfig.deferredPurchasesListener)
85+
verify { mockConfig.deferredPurchasesListener = listener }
8486
}
8587

8688
@Test
87-
fun `setting entitlements listener does not affect deferred listener`() {
88-
val deferredListener = mockk<QDeferredPurchasesListener>()
89-
internalConfig.deferredPurchasesListener = deferredListener
89+
fun `setDeferredPurchasesListener to null clears listener`() {
90+
every { mockConfig.deferredPurchasesListener = any() } just Runs
91+
every { mockConfig.deferredPurchasesListener } returns null
9092

91-
val entitlementsListener = mockk<QEntitlementsUpdateListener>()
92-
internalConfig.entitlementsUpdateListener = entitlementsListener
93+
mockConfig.deferredPurchasesListener = null
9394

94-
assertEquals(deferredListener, internalConfig.deferredPurchasesListener)
95+
assertNull(mockConfig.deferredPurchasesListener)
9596
}
9697
}

sdk/src/test/java/com/qonversion/android/sdk/internal/InternalConfigTest.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import com.qonversion.android.sdk.internal.dto.config.CacheConfig
77
import com.qonversion.android.sdk.internal.dto.config.PrimaryConfig
88
import com.qonversion.android.sdk.internal.provider.EnvironmentProvider
99
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
10-
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
1110
import io.mockk.mockk
1211
import org.assertj.core.api.Assertions.assertThat
1312
import org.junit.jupiter.api.BeforeEach
@@ -19,15 +18,13 @@ internal class InternalConfigTest {
1918

2019
private val mockPrimaryConfig = mockk<PrimaryConfig>()
2120
private val mockCacheConfig = mockk<CacheConfig>()
22-
private val mockEntitlementsUpdateListener = mockk<QEntitlementsUpdateListener>()
2321
private val mockDeferredPurchasesListener = mockk<QDeferredPurchasesListener>()
2422

2523
@BeforeEach
2624
fun setUp() {
2725
internalConfig = InternalConfig(
2826
mockPrimaryConfig,
2927
mockCacheConfig,
30-
mockEntitlementsUpdateListener,
3128
mockDeferredPurchasesListener
3229
)
3330
}
@@ -47,26 +44,23 @@ internal class InternalConfigTest {
4744
// then
4845
assertThat(internalConfig.primaryConfig).isSameAs(mockPrimaryConfig)
4946
assertThat(internalConfig.cacheConfig).isSameAs(mockCacheConfig)
50-
assertThat(internalConfig.entitlementsUpdateListener).isNull()
5147
assertThat(internalConfig.deferredPurchasesListener).isNull()
5248
}
5349

5450
@Test
55-
fun `primary constructor with both listeners`() {
51+
fun `primary constructor with deferred purchases listener`() {
5652
// given
5753

5854
// when
5955
val internalConfig = InternalConfig(
6056
mockPrimaryConfig,
6157
mockCacheConfig,
62-
mockEntitlementsUpdateListener,
6358
mockDeferredPurchasesListener
6459
)
6560

6661
// then
6762
assertThat(internalConfig.primaryConfig).isSameAs(mockPrimaryConfig)
6863
assertThat(internalConfig.cacheConfig).isSameAs(mockCacheConfig)
69-
assertThat(internalConfig.entitlementsUpdateListener).isSameAs(mockEntitlementsUpdateListener)
7064
assertThat(internalConfig.deferredPurchasesListener).isSameAs(mockDeferredPurchasesListener)
7165
}
7266

@@ -77,7 +71,6 @@ internal class InternalConfigTest {
7771
mockk(),
7872
mockPrimaryConfig,
7973
mockCacheConfig,
80-
mockEntitlementsUpdateListener,
8174
mockDeferredPurchasesListener
8275
)
8376

@@ -87,7 +80,6 @@ internal class InternalConfigTest {
8780
// then
8881
assertThat(internalConfig.primaryConfig).isSameAs(mockPrimaryConfig)
8982
assertThat(internalConfig.cacheConfig).isSameAs(mockCacheConfig)
90-
assertThat(internalConfig.entitlementsUpdateListener).isSameAs(mockEntitlementsUpdateListener)
9183
assertThat(internalConfig.deferredPurchasesListener).isSameAs(mockDeferredPurchasesListener)
9284
}
9385
}

0 commit comments

Comments
 (0)