From 44191823109908c61e7eb98e08079c8fa5c56ba3 Mon Sep 17 00:00:00 2001 From: Kamo Spertsyan Date: Wed, 2 Aug 2023 12:09:49 +0300 Subject: [PATCH] New get and send properties requests (#504) * New user properties POST request support. * New user properties GET request support. * Detekt fixes * Detekt fixes * Tests fixes * CR fixes * CR fixes * iOS property added. * Integration tests for `getProperties`. * Detekt fixes --- .../java/com/qonversion/android/app/App.java | 6 +- .../android/app/EntitlementsAdapter.kt | 2 +- .../android/app/EntitlementsFragment.kt | 2 +- .../qonversion/android/app/HomeFragment.kt | 4 +- .../android/app/OfferingsFragment.kt | 2 +- config/detekt/baseline.xml | 28 +++---- .../sdk/internal/OutagerIntegrationTest.kt | 27 ++++++- .../QonversionRepositoryIntegrationTest.kt | 77 +++++++++++++++++-- .../com/qonversion/android/sdk/Qonversion.kt | 22 ++++-- .../android/sdk/QonversionConfig.kt | 2 +- .../sdk/automations/mvp/ScreenFragment.kt | 2 +- .../android/sdk/dto/QUserProperty.kt | 13 ---- .../dto/{ => entitlements}/QEntitlement.kt | 2 +- .../QEntitlementRenewState.kt | 2 +- .../{ => entitlements}/QEntitlementSource.kt | 2 +- .../QEntitlementsCacheLifetime.kt | 2 +- .../sdk/dto/properties/QUserProperties.kt | 62 +++++++++++++++ .../sdk/dto/properties/QUserProperty.kt | 16 ++++ .../sdk/dto/properties/QUserPropertyKey.kt | 20 +++++ .../sdk/internal/QProductCenterManager.kt | 4 +- .../sdk/internal/QUserPropertiesManager.kt | 34 ++++++-- .../sdk/internal/QonversionInternal.kt | 18 +++-- .../sdk/internal/QonversionRepository.kt | 49 +++++++++--- .../android/sdk/internal/api/Api.kt | 16 +++- .../android/sdk/internal/dto/QPermission.kt | 2 +- .../internal/dto/QonversionMappingAdapters.kt | 2 +- .../sdk/internal/dto/SendPropertiesResult.kt | 17 ++++ .../sdk/internal/dto/config/CacheConfig.kt | 2 +- .../internal/dto/request/PropertiesRequest.kt | 11 --- .../request/data/UserPropertyRequestData.kt | 10 +++ .../android/sdk/internal/extensions.kt | 2 +- .../listeners/QEntitlementsUpdateListener.kt | 2 +- .../sdk/listeners/QonversionCallback.kt | 8 +- .../android/sdk/QonversionConfigTest.kt | 2 +- .../internal/QUserPropertiesManagerTest.kt | 31 +++++--- .../requests/PropertiesRequestTest.kt | 22 ------ .../android/sdk/internal/storage/util.kt | 3 +- 37 files changed, 381 insertions(+), 147 deletions(-) delete mode 100644 sdk/src/main/java/com/qonversion/android/sdk/dto/QUserProperty.kt rename sdk/src/main/java/com/qonversion/android/sdk/dto/{ => entitlements}/QEntitlement.kt (93%) rename sdk/src/main/java/com/qonversion/android/sdk/dto/{ => entitlements}/QEntitlementRenewState.kt (95%) rename sdk/src/main/java/com/qonversion/android/sdk/dto/{ => entitlements}/QEntitlementSource.kt (89%) rename sdk/src/main/java/com/qonversion/android/sdk/dto/{ => entitlements}/QEntitlementsCacheLifetime.kt (80%) create mode 100644 sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperties.kt create mode 100644 sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperty.kt create mode 100644 sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserPropertyKey.kt create mode 100644 sdk/src/main/java/com/qonversion/android/sdk/internal/dto/SendPropertiesResult.kt delete mode 100644 sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/PropertiesRequest.kt create mode 100644 sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/data/UserPropertyRequestData.kt delete mode 100644 sdk/src/test/java/com/qonversion/android/sdk/internal/requests/PropertiesRequestTest.kt diff --git a/app/src/main/java/com/qonversion/android/app/App.java b/app/src/main/java/com/qonversion/android/app/App.java index 2ff31b425..a14411278 100644 --- a/app/src/main/java/com/qonversion/android/app/App.java +++ b/app/src/main/java/com/qonversion/android/app/App.java @@ -14,7 +14,7 @@ import com.qonversion.android.sdk.dto.QEnvironment; import com.qonversion.android.sdk.dto.QLaunchMode; import com.qonversion.android.sdk.dto.QAttributionProvider; -import com.qonversion.android.sdk.dto.QUserProperty; +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey; import java.util.Map; @@ -42,8 +42,8 @@ public void onCreate() { @Override public void onConversionDataSuccess(final Map conversionData) { - Qonversion.getSharedInstance().setProperty( - QUserProperty.AppsFlyerUserId, + Qonversion.getSharedInstance().setUserProperty( + QUserPropertyKey.AppsFlyerUserId, AppsFlyerLib.getInstance().getAppsFlyerUID(App.this) ); Qonversion.getSharedInstance().attribution(conversionData, QAttributionProvider.AppsFlyer); diff --git a/app/src/main/java/com/qonversion/android/app/EntitlementsAdapter.kt b/app/src/main/java/com/qonversion/android/app/EntitlementsAdapter.kt index ded96cbb6..22dd15610 100644 --- a/app/src/main/java/com/qonversion/android/app/EntitlementsAdapter.kt +++ b/app/src/main/java/com/qonversion/android/app/EntitlementsAdapter.kt @@ -4,7 +4,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.app.databinding.TableRowEntitlementBinding class EntitlementsAdapter(private val entitlements: List) : diff --git a/app/src/main/java/com/qonversion/android/app/EntitlementsFragment.kt b/app/src/main/java/com/qonversion/android/app/EntitlementsFragment.kt index 6907d3f2d..d51f9fa2a 100644 --- a/app/src/main/java/com/qonversion/android/app/EntitlementsFragment.kt +++ b/app/src/main/java/com/qonversion/android/app/EntitlementsFragment.kt @@ -11,7 +11,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.qonversion.android.app.databinding.FragmentEntitlementsBinding import com.qonversion.android.sdk.Qonversion -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.listeners.QonversionEntitlementsCallback diff --git a/app/src/main/java/com/qonversion/android/app/HomeFragment.kt b/app/src/main/java/com/qonversion/android/app/HomeFragment.kt index febaf48cd..89a98c291 100644 --- a/app/src/main/java/com/qonversion/android/app/HomeFragment.kt +++ b/app/src/main/java/com/qonversion/android/app/HomeFragment.kt @@ -22,14 +22,12 @@ import com.qonversion.android.sdk.automations.dto.QActionResult import com.qonversion.android.sdk.automations.dto.QActionResultType import com.qonversion.android.sdk.automations.dto.QScreenPresentationConfig import com.qonversion.android.sdk.automations.dto.QScreenPresentationStyle -import com.qonversion.android.sdk.dto.QEntitlement -import com.qonversion.android.sdk.dto.QRemoteConfig +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.products.QProduct import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener import com.qonversion.android.sdk.listeners.QonversionEntitlementsCallback import com.qonversion.android.sdk.listeners.QonversionProductsCallback -import com.qonversion.android.sdk.listeners.QonversionRemoteConfigCallback private const val TAG = "HomeFragment" diff --git a/app/src/main/java/com/qonversion/android/app/OfferingsFragment.kt b/app/src/main/java/com/qonversion/android/app/OfferingsFragment.kt index 971b9bfd0..588df5eda 100644 --- a/app/src/main/java/com/qonversion/android/app/OfferingsFragment.kt +++ b/app/src/main/java/com/qonversion/android/app/OfferingsFragment.kt @@ -14,7 +14,7 @@ import com.qonversion.android.sdk.Qonversion import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.listeners.QonversionOfferingsCallback import com.qonversion.android.sdk.listeners.QonversionEntitlementsCallback -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.offerings.QOfferings import com.qonversion.android.sdk.dto.products.QProduct diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 5a62fd638..75b9a8318 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -37,7 +37,6 @@ FinalNewline:com.qonversion.android.sdk.internal.requests.EnvironmentRequestTest.kt:1 FinalNewline:com.qonversion.android.sdk.internal.requests.InitRequestTest.kt:1 FinalNewline:com.qonversion.android.sdk.internal.requests.OsRequestTest.kt:1 - FinalNewline:com.qonversion.android.sdk.internal.requests.PropertiesRequestTest.kt:1 FinalNewline:com.qonversion.android.sdk.internal.requests.ProviderDataRequestTest.kt:1 FinalNewline:com.qonversion.android.sdk.internal.requests.queue.RequestQueueTest.kt:1 FinalNewline:com.qonversion.android.sdk.internal.requests.queue.util.kt:1 @@ -51,7 +50,6 @@ ForbiddenComment:AttributionRequestTest.kt$AttributionRequestTest$// TODO: Update test for new AttributionRequest format ForbiddenComment:EnvironmentRequestTest.kt$EnvironmentRequestTest$// TODO: Update test for new Environment format ForbiddenComment:InitRequestTest.kt$InitRequestTest$// TODO: Update test for new InitRequest format - ForbiddenComment:PropertiesRequestTest.kt$PropertiesRequestTest$// TODO: Update test for new PropertiesRequest format ForbiddenComment:RequestQueueTest.kt$RequestQueueTest$// TODO: Update test for new AttributionRequest format ForbiddenComment:RequestValidatorTest.kt$RequestValidatorTest$// TODO: Update test for new AttributionRequest format ForbiddenComment:util.kt$Util.Companion$// TODO: Update test for new AttributionRequest format @@ -193,18 +191,18 @@ MaximumLineLength:com.qonversion.android.sdk.automations.mvp.ScreenPresenterTest.kt:159 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:112 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:230 - MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:360 - MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:419 + MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:381 + MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:440 MaximumLineLength:com.qonversion.android.sdk.internal.OutagerIntegrationTest.kt:88 MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:159 MaximumLineLength:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:161 - MaximumLineLength:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:172 + MaximumLineLength:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:174 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:115 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:323 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:386 - MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:656 - MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:877 + MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:717 MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:91 + MaximumLineLength:com.qonversion.android.sdk.internal.QonversionRepositoryIntegrationTest.kt:938 MaximumLineLength:com.qonversion.android.sdk.internal.api.ApiErrorMapper.kt:117 MaximumLineLength:com.qonversion.android.sdk.internal.api.ApiErrorMapper.kt:118 MaximumLineLength:com.qonversion.android.sdk.internal.billing.QonversionBillingService.kt:109 @@ -231,8 +229,8 @@ MaximumLineLength:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:224 MaximumLineLength:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:225 MaximumLineLength:com.qonversion.android.sdk.internal.storage.TokenExtractorTest.kt:19 + MaximumLineLength:com.qonversion.android.sdk.internal.storage.util.kt:134 MaximumLineLength:com.qonversion.android.sdk.internal.storage.util.kt:135 - MaximumLineLength:com.qonversion.android.sdk.internal.storage.util.kt:136 NestedBlockDepth:ApiErrorMapper.kt$ApiErrorMapper$fun <T> getErrorFromResponse(value: Response<T>): QonversionError NewLineAtEndOfFile:ApiHelperTest.kt$com.qonversion.android.sdk.internal.api.ApiHelperTest.kt NewLineAtEndOfFile:AppRequestTest.kt$com.qonversion.android.sdk.internal.requests.AppRequestTest.kt @@ -245,7 +243,6 @@ NewLineAtEndOfFile:InitRequestTest.kt$com.qonversion.android.sdk.internal.requests.InitRequestTest.kt NewLineAtEndOfFile:InternalConfigTest.kt$com.qonversion.android.sdk.internal.InternalConfigTest.kt NewLineAtEndOfFile:OsRequestTest.kt$com.qonversion.android.sdk.internal.requests.OsRequestTest.kt - NewLineAtEndOfFile:PropertiesRequestTest.kt$com.qonversion.android.sdk.internal.requests.PropertiesRequestTest.kt NewLineAtEndOfFile:ProviderDataRequestTest.kt$com.qonversion.android.sdk.internal.requests.ProviderDataRequestTest.kt NewLineAtEndOfFile:PurchasesCacheTest.kt$com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt NewLineAtEndOfFile:QAutomationsManagerTest.kt$com.qonversion.android.sdk.automations.internal.QAutomationsManagerTest.kt @@ -276,7 +273,6 @@ NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.storage.TokenStorageTest.kt:124 NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.storage.TokenStorageTest.kt:63 NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.validator.RequestValidatorTest.kt:3 - NoUnusedImports:com.qonversion.android.sdk.internal.storage.util.kt:4 NoWildcardImports:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:12 NoWildcardImports:com.qonversion.android.sdk.automations.internal.AutomationsEventMapperTest.kt:6 NoWildcardImports:com.qonversion.android.sdk.automations.internal.QAutomationsManagerTest.kt:23 @@ -299,9 +295,8 @@ NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:21 NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:29 NoWildcardImports:com.qonversion.android.sdk.internal.QProductCenterManagerTest.kt:9 - NoWildcardImports:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:15 - NoWildcardImports:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:16 - NoWildcardImports:com.qonversion.android.sdk.internal.api.ApiHeadersProvider.kt:8 + NoWildcardImports:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:17 + NoWildcardImports:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:18 NoWildcardImports:com.qonversion.android.sdk.internal.billing.BillingService.kt:6 NoWildcardImports:com.qonversion.android.sdk.internal.billing.QonversionBillingService.kt:6 NoWildcardImports:com.qonversion.android.sdk.internal.billing.QonversionBillingServiceTest.kt:5 @@ -318,8 +313,7 @@ NoWildcardImports:com.qonversion.android.sdk.internal.storage.LaunchResultCacheWrapperTest.kt:7 NoWildcardImports:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:8 NoWildcardImports:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:5 - NoWildcardImports:com.qonversion.android.sdk.internal.storage.util.kt:28 - NoWildcardImports:com.qonversion.android.sdk.internal.storage.util.kt:3 + NoWildcardImports:com.qonversion.android.sdk.internal.storage.util.kt:27 NoWildcardImports:com.qonversion.android.sdk.utils.kt:4 ReturnCount:AutomationsEventMapper.kt$AutomationsEventMapper$fun getEventFromRemoteMessage(messageData: Map<String, String>): AutomationsEvent? ReturnCount:ExceptionHandler.kt$ExceptionHandler$private fun isQonversionException(exception: Throwable): Boolean @@ -341,9 +335,7 @@ SpacingAroundCurly:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverterTest.kt:145 SpacingAroundCurly:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverterTest.kt:297 SpacingAroundCurly:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverterTest.kt:303 - SpacingAroundCurly:com.qonversion.android.sdk.internal.requests.PropertiesRequestTest.kt:13 - SpacingAroundCurly:com.qonversion.android.sdk.internal.requests.PropertiesRequestTest.kt:19 - SpacingAroundParens:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:489 + SpacingAroundParens:com.qonversion.android.sdk.internal.QUserPropertiesManagerTest.kt:496 SpacingAroundParens:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverterTest.kt:207 SpacingAroundParens:com.qonversion.android.sdk.internal.converter.GooglePurchaseConverterTest.kt:308 SpacingAroundParens:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:212 diff --git a/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/OutagerIntegrationTest.kt b/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/OutagerIntegrationTest.kt index 13211dc56..226c71c75 100644 --- a/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/OutagerIntegrationTest.kt +++ b/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/OutagerIntegrationTest.kt @@ -7,9 +7,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.collect.Maps import com.qonversion.android.sdk.QonversionConfig import com.qonversion.android.sdk.dto.QAttributionProvider -import com.qonversion.android.sdk.dto.QEntitlementSource +import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource import com.qonversion.android.sdk.dto.QLaunchMode -import com.qonversion.android.sdk.dto.QUserProperty +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.QonversionErrorCode import com.qonversion.android.sdk.dto.eligibility.QEligibility @@ -321,7 +321,7 @@ internal class OutagerIntegrationTest { val signal = CountDownLatch(1) val testProperties = mapOf( "customProperty" to "custom property value", - QUserProperty.CustomUserId.userPropertyCode to "custom user id" + QUserPropertyKey.CustomUserId.userPropertyCode to "custom user id" ) val uid = UID_PREFIX + "_sendProperties" @@ -343,6 +343,27 @@ internal class OutagerIntegrationTest { signal.await() } + @Test + fun getProperties() { + // given + val signal = CountDownLatch(1) + + val uid = UID_PREFIX + "_sendProperties" + val repository = initRepository(uid) + + // when and then + repository.getProperties( + { fail("Shouldn't succeed") }, + { error -> + assertEquals(error.code, QonversionErrorCode.BackendError) + assertTrue("HTTP status code=503, error=Service Unavailable. " == error.additionalMessage) + signal.countDown() + } + ) + + signal.await() + } + @Test fun eligibilityForProductIds() { // given diff --git a/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/QonversionRepositoryIntegrationTest.kt b/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/QonversionRepositoryIntegrationTest.kt index dd7f084ce..2e26de9d4 100644 --- a/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/QonversionRepositoryIntegrationTest.kt +++ b/sdk/src/androidTest/java/com/qonversion/android/sdk/internal/QonversionRepositoryIntegrationTest.kt @@ -7,9 +7,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.collect.Maps import com.qonversion.android.sdk.QonversionConfig import com.qonversion.android.sdk.dto.QAttributionProvider -import com.qonversion.android.sdk.dto.QEntitlementSource +import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource import com.qonversion.android.sdk.dto.QLaunchMode -import com.qonversion.android.sdk.dto.QUserProperty +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.QonversionErrorCode import com.qonversion.android.sdk.dto.eligibility.QEligibility @@ -20,6 +20,7 @@ import com.qonversion.android.sdk.dto.offerings.QOfferings import com.qonversion.android.sdk.dto.products.QProduct import com.qonversion.android.sdk.dto.products.QProductDuration import com.qonversion.android.sdk.dto.products.QProductType +import com.qonversion.android.sdk.dto.properties.QUserProperty import com.qonversion.android.sdk.internal.di.QDependencyInjector import com.qonversion.android.sdk.internal.dto.QLaunchResult import com.qonversion.android.sdk.internal.dto.QPermission @@ -31,7 +32,6 @@ import com.qonversion.android.sdk.internal.purchase.Purchase import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback import com.qonversion.android.sdk.listeners.QonversionLaunchCallback import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Test @@ -473,7 +473,7 @@ internal class QonversionRepositoryIntegrationTest { val signal = CountDownLatch(1) val testProperties = mapOf( "customProperty" to "custom property value", - QUserProperty.CustomUserId.userPropertyCode to "custom user id" + QUserPropertyKey.CustomUserId.userPropertyCode to "custom user id" ) val uid = UID_PREFIX + "_sendProperties" @@ -501,7 +501,7 @@ internal class QonversionRepositoryIntegrationTest { val signal = CountDownLatch(1) val testProperties = mapOf( "customProperty" to "custom property value", - QUserProperty.CustomUserId.userPropertyCode to "custom user id" + QUserPropertyKey.CustomUserId.userPropertyCode to "custom user id" ) val uid = UID_PREFIX + "_sendProperties" @@ -510,10 +510,71 @@ internal class QonversionRepositoryIntegrationTest { // when and then repository.sendProperties( testProperties, - { fail("Shouldn't fail") }, + { fail("Shouldn't succeed") }, { error -> - assertNotNull(error) - assertIncorrectProjectKeyError(error!!) + assertAccessDeniedError(error) + signal.countDown() + } + ) + + signal.await() + } + + @Test + fun getProperties() { + // given + val signal = CountDownLatch(1) + val testProperties = mapOf( + "customProperty" to "custom property value", + QUserPropertyKey.CustomUserId.userPropertyCode to "custom user id" + ) + val expRes = listOf( + QUserProperty("customProperty", "customProperty"), + QUserProperty(QUserPropertyKey.CustomUserId.userPropertyCode, "custom user id"), + ) + + val uid = UID_PREFIX + "_getProperties" + val repository = initRepository(uid) + + // when and then + withNewUserCreated(repository) { error -> + error?.let { + fail("Failed to create user") + } + + repository.sendProperties( + testProperties, + { + repository.getProperties( + { properties -> + properties.equalsIgnoreOrder(expRes) + signal.countDown() + }, + { + fail("Shouldn't fail") + } + ) + }, + { fail("Failed to send properties") } + ) + } + + signal.await() + } + + @Test + fun getPropertiesError() { + // given + val signal = CountDownLatch(1) + + val uid = UID_PREFIX + "_getProperties" + val repository = initRepository(uid, INCORRECT_PROJECT_KEY) + + // when and then + repository.getProperties( + { fail("Shouldn't succeed") }, + { error -> + assertAccessDeniedError(error) signal.countDown() } ) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt index 65bf78ec6..5fa528ff1 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/Qonversion.kt @@ -4,7 +4,7 @@ import android.app.Activity import android.util.Log import com.android.billingclient.api.BillingFlowParams import com.qonversion.android.sdk.dto.QAttributionProvider -import com.qonversion.android.sdk.dto.QUserProperty +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.dto.products.QProduct import com.qonversion.android.sdk.internal.InternalConfig import com.qonversion.android.sdk.internal.QonversionInternal @@ -16,6 +16,7 @@ import com.qonversion.android.sdk.listeners.QonversionOfferingsCallback import com.qonversion.android.sdk.listeners.QonversionProductsCallback import com.qonversion.android.sdk.listeners.QonversionRemoteConfigCallback import com.qonversion.android.sdk.listeners.QonversionUserCallback +import com.qonversion.android.sdk.listeners.QonversionUserPropertiesCallback interface Qonversion { @@ -257,18 +258,29 @@ interface Qonversion { fun attribution(data: Map, provider: QAttributionProvider) /** - * Sets Qonversion reserved user properties, like email or one-signal id + * Sets Qonversion reserved user properties, like email or one-signal id. + * Note that using [QUserPropertyKey.Custom] here will do nothing. + * To set custom user property, use [setCustomUserProperty] method instead. * @param key defined enum key that will be transformed to string * @param value property value */ - fun setProperty(key: QUserProperty, value: String) + fun setUserProperty(key: QUserPropertyKey, value: String) /** - * Sets custom user properties + * Sets custom user property * @param key custom user property key * @param value property value */ - fun setUserProperty(key: String, value: String) + fun setCustomUserProperty(key: String, value: String) + + /** + * This method returns all the properties, set for the current Qonversion user. + * All set properties are sent to the server with delay, so if you call + * this function right after setting some property, it may not be included + * in the result. + * @param callback - callback that will be called when response is received + */ + fun userProperties(callback: QonversionUserPropertiesCallback) /** * Provide a listener to be notified about asynchronous user entitlements updates. diff --git a/sdk/src/main/java/com/qonversion/android/sdk/QonversionConfig.kt b/sdk/src/main/java/com/qonversion/android/sdk/QonversionConfig.kt index 85450c4c0..b4e7018a4 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/QonversionConfig.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/QonversionConfig.kt @@ -5,7 +5,7 @@ import android.util.Log import com.qonversion.android.sdk.dto.QEnvironment import com.qonversion.android.sdk.dto.QLaunchMode import android.content.Context -import com.qonversion.android.sdk.dto.QEntitlementsCacheLifetime +import com.qonversion.android.sdk.dto.entitlements.QEntitlementsCacheLifetime 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 diff --git a/sdk/src/main/java/com/qonversion/android/sdk/automations/mvp/ScreenFragment.kt b/sdk/src/main/java/com/qonversion/android/sdk/automations/mvp/ScreenFragment.kt index eb2b30696..b3ed2b471 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/automations/mvp/ScreenFragment.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/automations/mvp/ScreenFragment.kt @@ -17,7 +17,7 @@ import com.qonversion.android.sdk.automations.dto.QActionResultType import com.qonversion.android.sdk.automations.internal.QAutomationsManager import com.qonversion.android.sdk.automations.internal.macros.ScreenProcessor import com.qonversion.android.sdk.databinding.QFragmentScreenBinding -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.QonversionErrorCode import com.qonversion.android.sdk.internal.di.QDependencyInjector diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QUserProperty.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/QUserProperty.kt deleted file mode 100644 index 04da5df84..000000000 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QUserProperty.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.qonversion.android.sdk.dto - -enum class QUserProperty(val userPropertyCode: String) { - Email("_q_email"), - Name("_q_name"), - KochavaDeviceId("_q_kochava_device_id"), - AppsFlyerUserId("_q_appsflyer_user_id"), - AdjustAdId("_q_adjust_adid"), - CustomUserId("_q_custom_user_id"), - FacebookAttribution("_q_fb_attribution"), - FirebaseAppInstanceId("_q_firebase_instance_id"), - AppSetId("_q_app_set_id"); -} diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlement.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlement.kt similarity index 93% rename from sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlement.kt rename to sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlement.kt index 47e14d1e5..d5d140cac 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlement.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlement.kt @@ -1,4 +1,4 @@ -package com.qonversion.android.sdk.dto +package com.qonversion.android.sdk.dto.entitlements import com.qonversion.android.sdk.internal.dto.QPermission import java.util.Date diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementRenewState.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementRenewState.kt similarity index 95% rename from sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementRenewState.kt rename to sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementRenewState.kt index bfd1a97d1..740efd623 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementRenewState.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementRenewState.kt @@ -1,4 +1,4 @@ -package com.qonversion.android.sdk.dto +package com.qonversion.android.sdk.dto.entitlements import com.qonversion.android.sdk.internal.dto.QProductRenewState diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementSource.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementSource.kt similarity index 89% rename from sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementSource.kt rename to sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementSource.kt index 8ae0ffdc3..1e5de601b 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementSource.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementSource.kt @@ -1,4 +1,4 @@ -package com.qonversion.android.sdk.dto +package com.qonversion.android.sdk.dto.entitlements enum class QEntitlementSource(internal val key: String) { Unknown("unknown"), // Unable to detect the source diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementsCacheLifetime.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementsCacheLifetime.kt similarity index 80% rename from sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementsCacheLifetime.kt rename to sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementsCacheLifetime.kt index ca1de5ca3..ed701c3b0 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/dto/QEntitlementsCacheLifetime.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementsCacheLifetime.kt @@ -1,4 +1,4 @@ -package com.qonversion.android.sdk.dto +package com.qonversion.android.sdk.dto.entitlements enum class QEntitlementsCacheLifetime(val days: Int) { Week(7), diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperties.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperties.kt new file mode 100644 index 000000000..0ab4dd1c7 --- /dev/null +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperties.kt @@ -0,0 +1,62 @@ +package com.qonversion.android.sdk.dto.properties + +import com.qonversion.android.sdk.Qonversion + +data class QUserProperties( + /** + * List of all user properties. + */ + val properties: List +) { + /** + * List of user properties, set for the Qonversion defined keys. + * This is a subset of all [properties] list. + * @see [Qonversion.setUserProperty] + */ + val definedProperties: List = properties + .filter { it.definedKey !== QUserPropertyKey.Custom } + + /** + * List of user properties, set for custom keys. + * This is a subset of all [properties] list. + * @see [Qonversion.setCustomUserProperty] + */ + val customProperties: List = properties + .filter { it.definedKey === QUserPropertyKey.Custom } + + /** + * Map of all user properties. + * This is a flattened version of the [properties] list as a key-value map. + */ + val flatPropertiesMap: Map = properties + .associate { it.key to it.value } + + /** + * Map of user properties, set for the Qonversion defined keys. + * This is a flattened version of the [definedProperties] list as a key-value map. + * @see [Qonversion.setUserProperty] + */ + val flatDefinedPropertiesMap: Map = definedProperties + .associate { it.definedKey to it.value } + + /** + * Map of user properties, set for custom keys. + * This is a flattened version of the [customProperties] list as a key-value map. + * @see [Qonversion.setCustomUserProperty] + */ + val flatCustomPropertiesMap: Map = customProperties + .associate { it.key to it.value } + + /** + * Searches for a property with the given property [key] in all properties list. + */ + fun getProperty(key: String): QUserProperty? = + properties.find { it.key == key } + + /** + * Searches for a property with the given Qonversion defined property [key] + * in defined properties list. + */ + fun getDefinedProperty(key: QUserPropertyKey): QUserProperty? = + definedProperties.find { it.definedKey === key } +} diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperty.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperty.kt new file mode 100644 index 000000000..22eb4186b --- /dev/null +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperty.kt @@ -0,0 +1,16 @@ +package com.qonversion.android.sdk.dto.properties + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class QUserProperty( + @Json(name = "key") val key: String, + @Json(name = "value") val value: String +) { + /** + * [QUserPropertyKey] used to set this property. + * Returns [QUserPropertyKey.Custom] for custom properties. + */ + val definedKey: QUserPropertyKey = QUserPropertyKey.fromString(key) +} diff --git a/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserPropertyKey.kt b/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserPropertyKey.kt new file mode 100644 index 000000000..84f0d8df4 --- /dev/null +++ b/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserPropertyKey.kt @@ -0,0 +1,20 @@ +package com.qonversion.android.sdk.dto.properties + +enum class QUserPropertyKey(val userPropertyCode: String) { + Email("_q_email"), + Name("_q_name"), + KochavaDeviceId("_q_kochava_device_id"), + AppsFlyerUserId("_q_appsflyer_user_id"), + AdjustAdId("_q_adjust_adid"), + CustomUserId("_q_custom_user_id"), + FacebookAttribution("_q_fb_attribution"), + FirebaseAppInstanceId("_q_firebase_instance_id"), + AppSetId("_q_app_set_id"), + AdvertisingID("_q_advertising_id"), // iOS only + Custom(""); + + companion object { + internal fun fromString(key: String) = + values().find { it.userPropertyCode == key } ?: Custom + } +} diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt index 365999765..1674e916a 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QProductCenterManager.kt @@ -8,7 +8,7 @@ import android.util.Pair import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.Purchase import com.android.billingclient.api.* -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.QonversionErrorCode @@ -26,7 +26,7 @@ import com.qonversion.android.sdk.internal.converter.GooglePurchaseConverter import com.qonversion.android.sdk.internal.converter.PurchaseConverter import com.qonversion.android.sdk.internal.dto.QLaunchResult import com.qonversion.android.sdk.internal.dto.QPermission -import com.qonversion.android.sdk.dto.QEntitlementSource +import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource import com.qonversion.android.sdk.dto.QUser import com.qonversion.android.sdk.dto.eligibility.QEligibility import com.qonversion.android.sdk.dto.offerings.QOfferings diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QUserPropertiesManager.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QUserPropertiesManager.kt index b6a241851..a5fe1333c 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QUserPropertiesManager.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QUserPropertiesManager.kt @@ -4,14 +4,16 @@ import android.app.Application import android.os.Handler import android.os.HandlerThread import androidx.annotation.VisibleForTesting -import com.qonversion.android.sdk.dto.QUserProperty +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.QonversionErrorCode +import com.qonversion.android.sdk.dto.properties.QUserProperties import com.qonversion.android.sdk.listeners.QonversionLaunchCallback import com.qonversion.android.sdk.internal.dto.QLaunchResult import com.qonversion.android.sdk.internal.logger.Logger import com.qonversion.android.sdk.internal.provider.AppStateProvider import com.qonversion.android.sdk.internal.storage.PropertiesStorage +import com.qonversion.android.sdk.listeners.QonversionUserPropertiesCallback import javax.inject.Inject internal class QUserPropertiesManager @Inject internal constructor( @@ -61,7 +63,7 @@ internal class QUserPropertiesManager @Inject internal constructor( override fun onFbAttributionIdResult(id: String?) { id ?: return - setUserProperty(QUserProperty.FacebookAttribution.userPropertyCode, id) + setCustomUserProperty(QUserPropertyKey.FacebookAttribution.userPropertyCode, id) } fun forceSendProperties() { @@ -76,16 +78,22 @@ internal class QUserPropertiesManager @Inject internal constructor( isSendingScheduled = false repository.sendProperties(properties, - onSuccess = { + onSuccess = { result -> + result.propertyErrors.forEach { propertyError -> + logger.release("Failed to save property ${propertyError.key}: ${propertyError.error}") + } + isRequestInProgress = false retriesCounter = 0 retryDelay = PROPERTY_UPLOAD_MIN_DELAY + + // Cleaning all the properties (not only succeeded) as we don't want to resend invalid ones again propertiesStorage.clear(properties) }, onError = { isRequestInProgress = false - if (it?.code === QonversionErrorCode.InvalidClientUid) { + if (it.code === QonversionErrorCode.InvalidClientUid) { productCenterManager?.launch(callback = object : QonversionLaunchCallback { override fun onSuccess(launchResult: QLaunchResult) { retryPropertiesRequest() @@ -114,11 +122,16 @@ internal class QUserPropertiesManager @Inject internal constructor( } } - fun setProperty(key: QUserProperty, value: String) { - setUserProperty(key.userPropertyCode, value) + fun setUserProperty(key: QUserPropertyKey, value: String) { + if (key === QUserPropertyKey.Custom) { + logger.release("Can not set user property with the key `QUserPropertyKey.Custom`. " + + "To set custom user property, use the `setCustomUserProperty` method.") + return + } + setCustomUserProperty(key.userPropertyCode, value) } - fun setUserProperty(key: String, value: String) { + fun setCustomUserProperty(key: String, value: String) { if (value.isEmpty()) { return } @@ -129,6 +142,13 @@ internal class QUserPropertiesManager @Inject internal constructor( } } + fun userProperties(callback: QonversionUserPropertiesCallback) { + repository.getProperties( + onSuccess = { properties -> callback.onSuccess(QUserProperties(properties)) }, + onError = { error -> callback.onError(error) } + ) + } + @VisibleForTesting fun sendPropertiesWithDelay(delaySec: Int) { if (appStateProvider.appState.isBackground()) { diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt index c71a2ad0a..0420de07b 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionInternal.kt @@ -9,9 +9,9 @@ import com.android.billingclient.api.BillingFlowParams import com.qonversion.android.sdk.Qonversion import com.qonversion.android.sdk.automations.internal.QAutomationsManager import com.qonversion.android.sdk.dto.QAttributionProvider -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.QRemoteConfig -import com.qonversion.android.sdk.dto.QUserProperty +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.dto.eligibility.QEligibility import com.qonversion.android.sdk.dto.offerings.QOfferings @@ -31,6 +31,7 @@ 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.QEntitlementsUpdateListener +import com.qonversion.android.sdk.listeners.QonversionUserPropertiesCallback internal class QonversionInternal( internalConfig: InternalConfig, @@ -292,13 +293,18 @@ internal class QonversionInternal( ?: logLaunchErrorForFunctionName(object {}.javaClass.enclosingMethod?.name) } - override fun setProperty(key: QUserProperty, value: String) { - userPropertiesManager?.setProperty(key, value) + override fun setUserProperty(key: QUserPropertyKey, value: String) { + userPropertiesManager?.setUserProperty(key, value) ?: logLaunchErrorForFunctionName(object {}.javaClass.enclosingMethod?.name) } - override fun setUserProperty(key: String, value: String) { - userPropertiesManager?.setUserProperty(key, value) + override fun setCustomUserProperty(key: String, value: String) { + userPropertiesManager?.setCustomUserProperty(key, value) + ?: logLaunchErrorForFunctionName(object {}.javaClass.enclosingMethod?.name) + } + + override fun userProperties(callback: QonversionUserPropertiesCallback) { + userPropertiesManager?.userProperties(callback) ?: logLaunchErrorForFunctionName(object {}.javaClass.enclosingMethod?.name) } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionRepository.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionRepository.kt index 05eeea1b0..538f38bb1 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionRepository.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/QonversionRepository.kt @@ -2,6 +2,7 @@ package com.qonversion.android.sdk.internal import android.content.SharedPreferences import androidx.annotation.VisibleForTesting +import com.qonversion.android.sdk.dto.properties.QUserProperty import com.qonversion.android.sdk.listeners.QonversionEligibilityCallback import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.listeners.QonversionLaunchCallback @@ -14,6 +15,7 @@ import com.qonversion.android.sdk.internal.billing.sku import com.qonversion.android.sdk.internal.dto.BaseResponse import com.qonversion.android.sdk.internal.dto.ProviderData import com.qonversion.android.sdk.internal.dto.QLaunchResult +import com.qonversion.android.sdk.internal.dto.SendPropertiesResult import com.qonversion.android.sdk.internal.dto.automations.ActionPointScreen import com.qonversion.android.sdk.internal.dto.automations.Screen import com.qonversion.android.sdk.internal.dto.eligibility.StoreProductInfo @@ -28,11 +30,11 @@ import com.qonversion.android.sdk.internal.dto.request.CrashRequest import com.qonversion.android.sdk.internal.dto.request.EligibilityRequest import com.qonversion.android.sdk.internal.dto.request.IdentityRequest import com.qonversion.android.sdk.internal.dto.request.InitRequest -import com.qonversion.android.sdk.internal.dto.request.PropertiesRequest import com.qonversion.android.sdk.internal.dto.request.PurchaseRequest import com.qonversion.android.sdk.internal.dto.request.RestoreRequest import com.qonversion.android.sdk.internal.dto.request.ViewsRequest import com.qonversion.android.sdk.internal.dto.request.data.InitRequestData +import com.qonversion.android.sdk.internal.dto.request.data.UserPropertyRequestData import com.qonversion.android.sdk.internal.purchase.Purchase import com.qonversion.android.sdk.internal.purchase.PurchaseHistory import com.qonversion.android.sdk.internal.logger.Logger @@ -185,23 +187,46 @@ internal class QonversionRepository internal constructor( fun sendProperties( properties: Map, - onSuccess: () -> Unit, - onError: (error: QonversionError?) -> Unit + onSuccess: (SendPropertiesResult) -> Unit, + onError: (error: QonversionError) -> Unit ) { - val propertiesRequest = PropertiesRequest( - accessToken = key, - clientUid = uid, - properties = properties - ) + val propertiesRequestData = properties.map { (key, value) -> UserPropertyRequestData(key, value) } + + api.sendProperties(uid, propertiesRequestData).enqueue { + onResponse = { + logger.debug("sendPropertiesRequest - ${it.getLogMessage()}") + + val body = it.body() + if (it.isSuccessful && body != null) { + onSuccess(body) + } else { + onError(errorMapper.getErrorFromResponse(it)) + } + } + onFailure = { + logger.debug("sendPropertiesRequest - failure - ${it.toQonversionError()}") + onError(it.toQonversionError()) + } + } + } - api.properties(propertiesRequest).enqueue { + fun getProperties( + onSuccess: (List) -> Unit, + onError: (error: QonversionError) -> Unit + ) { + api.getProperties(uid).enqueue { onResponse = { - logger.debug("propertiesRequest - ${it.getLogMessage()}") + logger.debug("getPropertiesRequest - ${it.getLogMessage()}") - if (it.isSuccessful) onSuccess() else onError(errorMapper.getErrorFromResponse(it)) + val body = it.body() + if (it.isSuccessful && body != null) { + onSuccess(body) + } else { + onError(errorMapper.getErrorFromResponse(it)) + } } onFailure = { - logger.debug("propertiesRequest - failure - ${it.toQonversionError()}") + logger.debug("getPropertiesRequest - failure - ${it.toQonversionError()}") onError(it.toQonversionError()) } } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/api/Api.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/api/Api.kt index 4812e8b58..3d09629d8 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/api/Api.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/api/Api.kt @@ -1,6 +1,7 @@ package com.qonversion.android.sdk.internal.api import com.qonversion.android.sdk.dto.QRemoteConfig +import com.qonversion.android.sdk.dto.properties.QUserProperty import com.qonversion.android.sdk.internal.Constants.CRASH_LOGS_URL import com.qonversion.android.sdk.internal.dto.automations.Screen import com.qonversion.android.sdk.internal.dto.eligibility.EligibilityResult @@ -10,6 +11,7 @@ import com.qonversion.android.sdk.internal.dto.BaseResponse import com.qonversion.android.sdk.internal.dto.Data import com.qonversion.android.sdk.internal.dto.QLaunchResult import com.qonversion.android.sdk.internal.dto.Response +import com.qonversion.android.sdk.internal.dto.SendPropertiesResult import com.qonversion.android.sdk.internal.dto.request.SendPushTokenRequest import com.qonversion.android.sdk.internal.dto.request.AttachUserRequest import com.qonversion.android.sdk.internal.dto.request.AttributionRequest @@ -17,10 +19,10 @@ import com.qonversion.android.sdk.internal.dto.request.CrashRequest import com.qonversion.android.sdk.internal.dto.request.EligibilityRequest import com.qonversion.android.sdk.internal.dto.request.IdentityRequest import com.qonversion.android.sdk.internal.dto.request.InitRequest -import com.qonversion.android.sdk.internal.dto.request.PropertiesRequest import com.qonversion.android.sdk.internal.dto.request.PurchaseRequest import com.qonversion.android.sdk.internal.dto.request.RestoreRequest import com.qonversion.android.sdk.internal.dto.request.ViewsRequest +import com.qonversion.android.sdk.internal.dto.request.data.UserPropertyRequestData import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -48,9 +50,6 @@ internal interface Api { @POST("attribution") fun attribution(@Body request: AttributionRequest): Call> - @POST("v1/properties") - fun properties(@Body request: PropertiesRequest): Call> - @POST("v1/products/get") fun eligibility(@Body request: EligibilityRequest): Call> @@ -90,4 +89,13 @@ internal interface Api { @Path("id") experimentId: String, @Path("user_id") userId: String ): Call + + @POST("v3/users/{user_id}/properties") + fun sendProperties( + @Path("user_id") userId: String, + @Body properties: List + ): Call + + @GET("v3/users/{user_id}/properties") + fun getProperties(@Path("user_id") userId: String): Call> } diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QPermission.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QPermission.kt index 34f40f5a2..7343adf37 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QPermission.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QPermission.kt @@ -1,6 +1,6 @@ package com.qonversion.android.sdk.internal.dto -import com.qonversion.android.sdk.dto.QEntitlementSource +import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource import com.qonversion.android.sdk.internal.toBoolean import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt index 13daeee82..b2af3c185 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QonversionMappingAdapters.kt @@ -1,6 +1,6 @@ package com.qonversion.android.sdk.internal.dto -import com.qonversion.android.sdk.dto.QEntitlementSource +import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource import com.qonversion.android.sdk.internal.milliSecondsToSeconds import com.qonversion.android.sdk.internal.secondsToMilliSeconds import com.qonversion.android.sdk.internal.dto.eligibility.ProductEligibility diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/SendPropertiesResult.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/SendPropertiesResult.kt new file mode 100644 index 000000000..b55737933 --- /dev/null +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/SendPropertiesResult.kt @@ -0,0 +1,17 @@ +package com.qonversion.android.sdk.internal.dto + +import com.qonversion.android.sdk.dto.properties.QUserProperty +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SendPropertiesResult( + @Json(name = "savedProperties") val savedProperties: List, + @Json(name = "propertyErrors") val propertyErrors: List, +) { + @JsonClass(generateAdapter = true) + internal data class PropertyError( + @Json(name = "key") val key: String, + @Json(name = "error") val error: String + ) +} diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/config/CacheConfig.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/config/CacheConfig.kt index 4ae9d6ff6..7b428134a 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/config/CacheConfig.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/config/CacheConfig.kt @@ -1,6 +1,6 @@ package com.qonversion.android.sdk.internal.dto.config -import com.qonversion.android.sdk.dto.QEntitlementsCacheLifetime +import com.qonversion.android.sdk.dto.entitlements.QEntitlementsCacheLifetime internal data class CacheConfig( val entitlementsCacheLifetime: QEntitlementsCacheLifetime diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/PropertiesRequest.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/PropertiesRequest.kt deleted file mode 100644 index 576d1cb01..000000000 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/PropertiesRequest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.qonversion.android.sdk.internal.dto.request - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class PropertiesRequest( - @Json(name = "access_token") val accessToken: String, - @Json(name = "q_uid") val clientUid: String?, - @Json(name = "properties") val properties: Map -) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/data/UserPropertyRequestData.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/data/UserPropertyRequestData.kt new file mode 100644 index 000000000..c340f1c5e --- /dev/null +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/data/UserPropertyRequestData.kt @@ -0,0 +1,10 @@ +package com.qonversion.android.sdk.internal.dto.request.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class UserPropertyRequestData( + @Json(name = "key") val key: String, + @Json(name = "value") val value: String +) diff --git a/sdk/src/main/java/com/qonversion/android/sdk/internal/extensions.kt b/sdk/src/main/java/com/qonversion/android/sdk/internal/extensions.kt index b4934a09d..7ebdbeede 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/internal/extensions.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/internal/extensions.kt @@ -3,7 +3,7 @@ package com.qonversion.android.sdk.internal import android.app.Application import android.content.Context import android.content.pm.ApplicationInfo -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.internal.dto.QPermission import org.json.JSONArray import org.json.JSONObject diff --git a/sdk/src/main/java/com/qonversion/android/sdk/listeners/QEntitlementsUpdateListener.kt b/sdk/src/main/java/com/qonversion/android/sdk/listeners/QEntitlementsUpdateListener.kt index ae8fbdde8..c228d0c60 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/listeners/QEntitlementsUpdateListener.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/listeners/QEntitlementsUpdateListener.kt @@ -1,6 +1,6 @@ package com.qonversion.android.sdk.listeners -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement /** * The listener of user entitlements updates. diff --git a/sdk/src/main/java/com/qonversion/android/sdk/listeners/QonversionCallback.kt b/sdk/src/main/java/com/qonversion/android/sdk/listeners/QonversionCallback.kt index f0ce6778f..687c0ec88 100644 --- a/sdk/src/main/java/com/qonversion/android/sdk/listeners/QonversionCallback.kt +++ b/sdk/src/main/java/com/qonversion/android/sdk/listeners/QonversionCallback.kt @@ -1,6 +1,6 @@ package com.qonversion.android.sdk.listeners -import com.qonversion.android.sdk.dto.QEntitlement +import com.qonversion.android.sdk.dto.entitlements.QEntitlement import com.qonversion.android.sdk.dto.QRemoteConfig import com.qonversion.android.sdk.dto.QonversionError import com.qonversion.android.sdk.internal.dto.QLaunchResult @@ -8,6 +8,7 @@ import com.qonversion.android.sdk.dto.QUser import com.qonversion.android.sdk.dto.offerings.QOfferings import com.qonversion.android.sdk.dto.products.QProduct import com.qonversion.android.sdk.dto.eligibility.QEligibility +import com.qonversion.android.sdk.dto.properties.QUserProperties internal interface QonversionLaunchCallback { fun onSuccess(launchResult: QLaunchResult) @@ -53,3 +54,8 @@ interface QonversionUserCallback { fun onSuccess(user: QUser) fun onError(error: QonversionError) } + +interface QonversionUserPropertiesCallback { + fun onSuccess(userProperties: QUserProperties) + fun onError(error: QonversionError) +} diff --git a/sdk/src/test/java/com/qonversion/android/sdk/QonversionConfigTest.kt b/sdk/src/test/java/com/qonversion/android/sdk/QonversionConfigTest.kt index bb4da4e09..2b73b974a 100644 --- a/sdk/src/test/java/com/qonversion/android/sdk/QonversionConfigTest.kt +++ b/sdk/src/test/java/com/qonversion/android/sdk/QonversionConfigTest.kt @@ -5,7 +5,7 @@ import android.content.Context import android.util.Log import com.qonversion.android.sdk.dto.QEnvironment import com.qonversion.android.sdk.dto.QLaunchMode -import com.qonversion.android.sdk.dto.QEntitlementsCacheLifetime +import com.qonversion.android.sdk.dto.entitlements.QEntitlementsCacheLifetime 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 diff --git a/sdk/src/test/java/com/qonversion/android/sdk/internal/QUserPropertiesManagerTest.kt b/sdk/src/test/java/com/qonversion/android/sdk/internal/QUserPropertiesManagerTest.kt index 5b4eeb55a..945a19d3f 100644 --- a/sdk/src/test/java/com/qonversion/android/sdk/internal/QUserPropertiesManagerTest.kt +++ b/sdk/src/test/java/com/qonversion/android/sdk/internal/QUserPropertiesManagerTest.kt @@ -5,9 +5,11 @@ import android.content.ContentResolver import android.os.Handler import android.os.Looper import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.qonversion.android.sdk.dto.QUserProperty +import com.qonversion.android.sdk.dto.properties.QUserPropertyKey import com.qonversion.android.sdk.dto.QonversionError +import com.qonversion.android.sdk.dto.QonversionErrorCode import com.qonversion.android.sdk.getPrivateField +import com.qonversion.android.sdk.internal.dto.SendPropertiesResult import com.qonversion.android.sdk.internal.logger.Logger import com.qonversion.android.sdk.internal.provider.AppStateProvider import com.qonversion.android.sdk.mockPrivateField @@ -73,7 +75,7 @@ internal class QUserPropertiesManagerTest { // then verify(exactly = 1) { - spykPropertiesManager.setUserProperty( + spykPropertiesManager.setCustomUserProperty( "_q_fb_attribution", fbAttributionId ) @@ -90,7 +92,7 @@ internal class QUserPropertiesManagerTest { // then verify(exactly = 0) { - spykPropertiesManager.setUserProperty( + spykPropertiesManager.setCustomUserProperty( any(), any() ) @@ -264,10 +266,15 @@ internal class QUserPropertiesManagerTest { // given mockPropertiesStorage(properties) + val propertiesResult = SendPropertiesResult( + emptyList(), + emptyList() + ) + every { mockRepository.sendProperties(properties, captureLambda(), any()) } answers { - lambda<() -> Unit>().captured.invoke() + lambda<(SendPropertiesResult) -> Unit>().captured.invoke(propertiesResult) } // when @@ -298,16 +305,16 @@ internal class QUserPropertiesManagerTest { @Test fun setProperty() { // given - val key = QUserProperty.Email + val key = QUserPropertyKey.Email val value = "me@qonversion.io" val spykPropertiesManager = spyk(propertiesManager, recordPrivateCalls = true) // when - spykPropertiesManager.setProperty(key, value) + spykPropertiesManager.setUserProperty(key, value) // then verify { - spykPropertiesManager.setUserProperty("_q_email", value) + spykPropertiesManager.setCustomUserProperty("_q_email", value) } } @@ -318,7 +325,7 @@ internal class QUserPropertiesManagerTest { val value = "" // when - propertiesManager.setUserProperty(key, value) + propertiesManager.setCustomUserProperty(key, value) // then verify { @@ -341,7 +348,7 @@ internal class QUserPropertiesManagerTest { mockPostDelayed(handlerDelay) // when - spykPropertiesManager.setUserProperty(key, value) + spykPropertiesManager.setCustomUserProperty(key, value) // then verify(exactly = 0) { @@ -363,7 +370,7 @@ internal class QUserPropertiesManagerTest { } just Runs // when - propertiesManager.setUserProperty(key, value) + propertiesManager.setCustomUserProperty(key, value) // then verifyOrder { @@ -384,7 +391,7 @@ internal class QUserPropertiesManagerTest { every { mockAppStateProvider.appState } returns AppState.Background // when - propertiesManager.setUserProperty(key, value) + propertiesManager.setCustomUserProperty(key, value) // then val isSendingScheduled = propertiesManager.getPrivateField(fieldIsSendingScheduled) @@ -501,7 +508,7 @@ internal class QUserPropertiesManagerTest { every { mockRepository.sendProperties(properties, any(), captureLambda()) } answers { - lambda<(QonversionError?) -> Unit>().captured.invoke(null) + lambda<(QonversionError) -> Unit>().captured.invoke(QonversionError(QonversionErrorCode.BackendError)) } } diff --git a/sdk/src/test/java/com/qonversion/android/sdk/internal/requests/PropertiesRequestTest.kt b/sdk/src/test/java/com/qonversion/android/sdk/internal/requests/PropertiesRequestTest.kt deleted file mode 100644 index 08a376ce5..000000000 --- a/sdk/src/test/java/com/qonversion/android/sdk/internal/requests/PropertiesRequestTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.qonversion.android.sdk.internal.requests - -import com.qonversion.android.sdk.internal.dto.request.PropertiesRequest -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import org.junit.Before -import org.junit.Test - -internal class PropertiesRequestTest { - private lateinit var adapter: JsonAdapter - - @Before - fun setUp(){ - val moshi = Moshi.Builder().build() - adapter = moshi.adapter(PropertiesRequest::class.java) - } - - @Test - fun requestWithCorrectData(){ - // TODO: Update test for new PropertiesRequest format - } -} \ No newline at end of file diff --git a/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/util.kt b/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/util.kt index 07b58d567..8ff020008 100644 --- a/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/util.kt +++ b/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/util.kt @@ -1,7 +1,6 @@ package com.qonversion.android.sdk.internal.storage -import com.qonversion.android.sdk.dto.* -import com.qonversion.android.sdk.dto.experiments.QExperiment +import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource import com.qonversion.android.sdk.dto.offerings.QOffering import com.qonversion.android.sdk.dto.offerings.QOfferingTag import com.qonversion.android.sdk.dto.offerings.QOfferings