Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.qonversion.android.sdk.dto.properties.QUserPropertyKey
import com.qonversion.android.sdk.internal.InternalConfig
import com.qonversion.android.sdk.internal.QonversionInternal
import com.qonversion.android.sdk.listeners.QonversionExperimentAttachCallback
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback
import com.qonversion.android.sdk.listeners.QonversionEntitlementsCallback
Expand Down Expand Up @@ -364,5 +365,22 @@ interface Qonversion {
*
* @param entitlementsUpdateListener listener to be called when entitlements update.
*/
@Deprecated("Use setDeferredPurchasesListener instead", ReplaceWith("setDeferredPurchasesListener(listener)"))
fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener)

/**
* Provide a listener to be notified about deferred purchase completions.
*
* Deferred purchases happen when transactions require additional steps to complete,
* such as pending transactions on Google Play.
* This listener provides full transaction details, including for consumable products
* without associated entitlements.
*
* You may set this listener both *after* Qonversion SDK initializing
* with [Qonversion.setDeferredPurchasesListener] and *while* Qonversion initializing
* with [Qonversion.initialize]
*
* @param listener listener to be called when a deferred purchase completes.
*/
fun setDeferredPurchasesListener(listener: QDeferredPurchasesListener)
}
35 changes: 27 additions & 8 deletions sdk/src/main/java/com/qonversion/android/sdk/QonversionConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import com.qonversion.android.sdk.dto.QLaunchMode
import android.content.Context
import androidx.annotation.RawRes
import com.qonversion.android.sdk.dto.entitlements.QEntitlementsCacheLifetime
import com.qonversion.android.sdk.internal.EntitlementsUpdateListenerAdapter
import com.qonversion.android.sdk.internal.dto.config.CacheConfig
import com.qonversion.android.sdk.internal.dto.config.PrimaryConfig
import com.qonversion.android.sdk.internal.application
import com.qonversion.android.sdk.internal.isDebuggable
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener

/**
* This class contains all the available configurations for the initialization of Qonversion SDK.
Expand All @@ -26,7 +28,7 @@ class QonversionConfig internal constructor(
internal val application: Application,
internal val primaryConfig: PrimaryConfig,
internal val cacheConfig: CacheConfig,
internal val entitlementsUpdateListener: QEntitlementsUpdateListener?
internal val deferredPurchasesListener: QDeferredPurchasesListener? = null
) {

/**
Expand All @@ -47,7 +49,7 @@ class QonversionConfig internal constructor(
) {
internal var environment = QEnvironment.Production
internal var entitlementsCacheLifetime = QEntitlementsCacheLifetime.Month
internal var entitlementsUpdateListener: QEntitlementsUpdateListener? = null
internal var deferredPurchasesListener: QDeferredPurchasesListener? = null
internal var proxyUrl: String? = null
internal var isKidsMode: Boolean = false
internal var sendFbAttribution: Boolean = true
Expand Down Expand Up @@ -96,15 +98,32 @@ class QonversionConfig internal constructor(
/**
* Provide a listener to be notified about asynchronous user entitlements updates.
*
* Make sure you provide this listener for being up-to-date with the user entitlements.
* Else you can lose some important updates. Also, please, consider that this listener
* should live for the whole lifetime of the application.
* @deprecated Use [setDeferredPurchasesListener] instead. It provides full transaction
* details including product ID, transaction ID, and value.
*
* @param entitlementsUpdateListener listener to be called when entitlements update.
* @return builder instance for chain calls.
*/
@Deprecated("Use setDeferredPurchasesListener instead", ReplaceWith("setDeferredPurchasesListener(listener)"))
fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener): Builder = apply {
this.entitlementsUpdateListener = entitlementsUpdateListener
// Adapter pattern: wrap the legacy listener so the SDK internally works
// with a single listener type (QDeferredPurchasesListener).
this.deferredPurchasesListener = EntitlementsUpdateListenerAdapter(entitlementsUpdateListener)
}

/**
* Provide a listener to be notified about deferred purchase completions.
*
* Deferred purchases happen when transactions require additional steps to complete,
* such as pending transactions on Google Play.
* This listener provides full transaction details, including for consumable products
* without associated entitlements.
*
* @param listener listener to be called when a deferred purchase completes.
* @return builder instance for chain calls.
*/
fun setDeferredPurchasesListener(listener: QDeferredPurchasesListener): Builder = apply {
this.deferredPurchasesListener = listener
}

/**
Expand All @@ -128,7 +147,7 @@ class QonversionConfig internal constructor(

/**
* Use this function to enable Qonversion SDK Kids mode.
* With this mode activated, our SDK does not collect any information that violates Google Childrens Privacy Policy.
* With this mode activated, our SDK does not collect any information that violates Google Children's Privacy Policy.
*/
fun enableKidsMode(): Builder = apply {
this.isKidsMode = true
Expand Down Expand Up @@ -167,7 +186,7 @@ class QonversionConfig internal constructor(
context.application,
primaryConfig,
cacheConfig,
entitlementsUpdateListener
deferredPurchasesListener
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.qonversion.android.sdk.internal

import com.qonversion.android.sdk.dto.QPurchaseResult
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener

/**
* Adapter that wraps a deprecated [QEntitlementsUpdateListener] as a [QDeferredPurchasesListener].
*
* When the deprecated setEntitlementsUpdateListener() is called, this adapter allows
* QProductCenterManager to work with a single listener type internally
* (QDeferredPurchasesListener only), eliminating duplicate invocation logic.
*
* The adapter extracts entitlements from the [QPurchaseResult] and forwards them
* to the wrapped legacy listener via onEntitlementsUpdated().
*/
internal class EntitlementsUpdateListenerAdapter(
private val legacyListener: QEntitlementsUpdateListener
) : QDeferredPurchasesListener {

override fun deferredPurchaseCompleted(purchaseResult: QPurchaseResult) {
legacyListener.onEntitlementsUpdated(purchaseResult.entitlements)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,19 @@ import com.qonversion.android.sdk.dto.QEnvironment
import com.qonversion.android.sdk.dto.QLaunchMode
import com.qonversion.android.sdk.internal.dto.config.CacheConfig
import com.qonversion.android.sdk.internal.provider.CacheConfigProvider
import com.qonversion.android.sdk.internal.provider.EntitlementsUpdateListenerProvider

import com.qonversion.android.sdk.internal.provider.EnvironmentProvider
import com.qonversion.android.sdk.internal.provider.PrimaryConfigProvider
import com.qonversion.android.sdk.internal.provider.UidProvider
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener

internal class InternalConfig(
override var primaryConfig: PrimaryConfig,
override val cacheConfig: CacheConfig,
override var entitlementsUpdateListener: QEntitlementsUpdateListener?
var deferredPurchasesListener: QDeferredPurchasesListener? = null
) : EnvironmentProvider,
PrimaryConfigProvider,
CacheConfigProvider,
UidProvider,
EntitlementsUpdateListenerProvider {
UidProvider {

@Volatile
var fatalError: HttpError? = null
Expand All @@ -36,7 +33,7 @@ internal class InternalConfig(
constructor(qonversionConfig: QonversionConfig) : this(
qonversionConfig.primaryConfig,
qonversionConfig.cacheConfig,
qonversionConfig.entitlementsUpdateListener
qonversionConfig.deferredPurchasesListener
)

override val apiUrl: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.qonversion.android.sdk.internal.repository.QRepository
import com.qonversion.android.sdk.internal.services.QUserInfoService
import com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapper
import com.qonversion.android.sdk.internal.storage.PurchasesCache
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
import com.qonversion.android.sdk.listeners.QonversionUserCallback
import com.qonversion.android.sdk.listeners.QonversionPurchaseCallback
Expand Down Expand Up @@ -466,7 +467,11 @@ internal class QProductCenterManager internal constructor(
}

fun setEntitlementsUpdateListener(entitlementsUpdateListener: QEntitlementsUpdateListener) {
internalConfig.entitlementsUpdateListener = entitlementsUpdateListener
internalConfig.deferredPurchasesListener = EntitlementsUpdateListenerAdapter(entitlementsUpdateListener)
}

fun setDeferredPurchasesListener(listener: QDeferredPurchasesListener) {
internalConfig.deferredPurchasesListener = listener
}

override fun onPurchasesCompleted(purchases: List<Purchase>) {
Expand Down Expand Up @@ -563,7 +568,12 @@ internal class QProductCenterManager internal constructor(
)

val entitlements = permissions.toEntitlementsMap()
callback?.onResult(QPurchaseResult.successFromFallback(entitlements, purchase))
val purchaseResult = QPurchaseResult.successFromFallback(entitlements, purchase)
if (callback != null) {
callback.onResult(purchaseResult)
} else {
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(purchaseResult)
}
}

private fun failLocallyGrantingPurchasePermissionsWithError(
Expand Down Expand Up @@ -1058,8 +1068,8 @@ internal class QProductCenterManager internal constructor(
removePurchaseOptions(product?.storeId)

if (purchaseCallback == null) {
// If no callback, notify entitlements update listener
internalConfig.entitlementsUpdateListener?.onEntitlementsUpdated(entitlements)
val purchaseResult = QPurchaseResult.success(entitlements, purchase)
internalConfig.deferredPurchasesListener?.deferredPurchaseCompleted(purchaseResult)
} else {
purchaseCallback.onResult(QPurchaseResult.success(entitlements, purchase))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.qonversion.android.sdk.listeners.QonversionProductsCallback
import com.qonversion.android.sdk.listeners.QonversionRemoteConfigCallback
import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback
import com.qonversion.android.sdk.listeners.QonversionUserCallback
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
import com.qonversion.android.sdk.listeners.QonversionRemoteConfigListCallback
import com.qonversion.android.sdk.listeners.QonversionRemoteConfigurationAttachCallback
Expand Down Expand Up @@ -377,6 +378,10 @@ internal class QonversionInternal(
productCenterManager.setEntitlementsUpdateListener(entitlementsUpdateListener)
}

override fun setDeferredPurchasesListener(listener: QDeferredPurchasesListener) {
productCenterManager.setDeferredPurchasesListener(listener)
}

// Private functions
private fun mainEntitlementsCallback(callback: QonversionEntitlementsCallback): QonversionEntitlementsCallback =
object : QonversionEntitlementsCallback {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.qonversion.android.sdk.listeners

import com.qonversion.android.sdk.dto.QPurchaseResult

/**
* The listener for deferred purchase completions.
*
* Deferred purchases happen when transactions require additional steps to complete,
* such as pending transactions on Google Play or purchases requiring additional access grants.
* This listener provides the purchase result with entitlements.
*
* It can be provided to the [QonversionConfig](com.qonversion.android.sdk.QonversionConfig)
* via [QonversionConfig.Builder.setDeferredPurchasesListener](com.qonversion.android.sdk.QonversionConfig.Builder.setDeferredPurchasesListener)
* or set directly to the current [Qonversion](com.qonversion.android.sdk.Qonversion) instance
* via [Qonversion.setDeferredPurchasesListener](com.qonversion.android.sdk.Qonversion.setDeferredPurchasesListener).
*/
interface QDeferredPurchasesListener {

/**
* Called when a deferred purchase completes.
*
* @param purchaseResult the purchase result containing entitlements granted by this purchase.
* It also contains the underlying Google Play [Purchase] object with
* productId, purchaseToken, orderId, etc.
*/
fun deferredPurchaseCompleted(purchaseResult: QPurchaseResult)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import com.qonversion.android.sdk.dto.entitlements.QEntitlement
/**
* The listener of user entitlements updates.
*
* It can be provided to the [QonversionConfig](com.qonversion.android.sdk.QonversionConfig)
* via [QonversionConfig.Builder.setEntitlementsUpdateListener](com.qonversion.android.sdk.QonversionConfig.Builder.setEntitlementsUpdateListener)
* or set directly to the current [Qonversion](com.qonversion.android.sdk.Qonversion) instance
* via [Qonversion.setEntitlementsUpdateListener](com.qonversion.android.sdk.Qonversion.setEntitlementsUpdateListener).
* @deprecated Use [QDeferredPurchasesListener] instead. It provides full transaction details
* including product ID, transaction ID, and value - critical for consumable products
* without entitlements.
*/
@Deprecated(
"Use QDeferredPurchasesListener instead",
ReplaceWith("QDeferredPurchasesListener")
)
interface QEntitlementsUpdateListener {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.qonversion.android.sdk.internal.application
import com.qonversion.android.sdk.internal.dto.config.CacheConfig
import com.qonversion.android.sdk.internal.dto.config.PrimaryConfig
import com.qonversion.android.sdk.internal.isDebuggable
import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
import com.qonversion.android.sdk.listeners.QDeferredPurchasesListener
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
Expand All @@ -32,7 +32,7 @@ internal class QonversionConfigTest {
private val projectKey = "some project key"
private val mockLaunchMode = mockk<QLaunchMode>()
private val mockEnvironment = mockk<QEnvironment>()
private val mockEntitlementsListener = mockk<QEntitlementsUpdateListener>()
private val mockDeferredPurchasesListener = mockk<QDeferredPurchasesListener>()
private val mockEntitlementsCacheLifetime = mockk<QEntitlementsCacheLifetime>()
private val mockProxyUrl = "mock url"
private val mockPrimaryConfig = PrimaryConfig(projectKey, mockLaunchMode, mockEnvironment, mockProxyUrl)
Expand All @@ -53,14 +53,14 @@ internal class QonversionConfigTest {
mockApplication,
mockPrimaryConfig,
mockCacheConfig,
mockEntitlementsListener
mockDeferredPurchasesListener
)

// then
assertThat(config.application).isSameAs(mockApplication)
assertThat(config.primaryConfig).isSameAs(mockPrimaryConfig)
assertThat(config.cacheConfig).isSameAs(mockCacheConfig)
assertThat(config.entitlementsUpdateListener).isSameAs(mockEntitlementsListener)
assertThat(config.deferredPurchasesListener).isSameAs(mockDeferredPurchasesListener)
}
}

Expand Down Expand Up @@ -164,14 +164,14 @@ internal class QonversionConfigTest {
.apply {
environment = mockEnvironment
entitlementsCacheLifetime = mockEntitlementsCacheLifetime
entitlementsUpdateListener = mockEntitlementsListener
deferredPurchasesListener = mockDeferredPurchasesListener
proxyUrl = mockProxyUrl
}
val expResult = QonversionConfig(
mockApplication,
mockPrimaryConfig,
mockCacheConfig,
mockEntitlementsListener
mockDeferredPurchasesListener
)

// when
Expand Down
Loading
Loading