From e4145d4dba12fd3c8d1f00d41691566a3b063aa6 Mon Sep 17 00:00:00 2001 From: slawomirzaba Date: Tue, 4 Feb 2025 14:44:47 +0100 Subject: [PATCH 1/3] Improve error handling in SDK --- .../java/de/contentpass/lib/Authorizer.kt | 6 ++- .../java/de/contentpass/lib/ContentPass.kt | 44 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/src/main/java/de/contentpass/lib/Authorizer.kt b/lib/src/main/java/de/contentpass/lib/Authorizer.kt index 18ce864..073ae26 100644 --- a/lib/src/main/java/de/contentpass/lib/Authorizer.kt +++ b/lib/src/main/java/de/contentpass/lib/Authorizer.kt @@ -51,7 +51,11 @@ internal class Authorizer( init { val context = Dispatchers.Default + Job() CoroutineScope(context).launch { - fetchConfig() + try { + fetchConfig() + } catch (e: Throwable) { + // this is allowed to fail + } } } diff --git a/lib/src/main/java/de/contentpass/lib/ContentPass.kt b/lib/src/main/java/de/contentpass/lib/ContentPass.kt index 771ecd0..2719b42 100644 --- a/lib/src/main/java/de/contentpass/lib/ContentPass.kt +++ b/lib/src/main/java/de/contentpass/lib/ContentPass.kt @@ -136,13 +136,23 @@ class ContentPass internal constructor( */ sealed class State { /** - * The contentpass object was just created. Will switch to another state very soon. + * The contentpass object was just created or an error recovery is ongoing. + * Will switch to another state very soon. * * After the stored contentpass token information is validated and refreshed, this will - * switch to either [Unauthenticated] or [Authenticated] + * switch to one of [Error], [Unauthenticated] or [Authenticated] */ object Initializing : State() + /** + * An error was encountered during token validation. + * + * This is probably due to a failing internet connection. You can check the exception and + * act accordingly. Once a stable network connection has been established, call [recoverFromError] + * to retry the token validation. + */ + class Error(val exception: Throwable): State() + /** * No user is currently authenticated. */ @@ -177,12 +187,15 @@ class ContentPass internal constructor( private val observers = mutableListOf() - private val coroutineContext = Dispatchers.Default + Job() + private val coroutineContext = Dispatchers.Default + SupervisorJob() init { + initializeAuthState() + } + + private fun initializeAuthState() { tokenStore.retrieveAuthState()?.let { authState = it - CoroutineScope(coroutineContext).launch { onNewAuthState(authState) } @@ -348,6 +361,19 @@ class ContentPass internal constructor( countSampledImpression() } } + /** + * Reinitializes this object's state. + * + * Call this function when you encountered an error during token validation, the current [state] + * is set to [Error] and you want to try the validation again. Commonly used when network access + * has been reestablished. + */ + fun recoverFromError() { + state = State.Initializing + + initializeAuthState() + } + private suspend fun countSampledImpression() { val generatedSample = Math.random() @@ -405,8 +431,14 @@ class ContentPass internal constructor( state = if (authState.isAuthorized) { setupRefreshTimer(authState)?.let { if (it) { - val hasSubscription = authorizer.validateSubscription(authState.idToken!!) - State.Authenticated(hasSubscription) + authState.idToken?.let { idToken -> + try { + val hasSubscription = authorizer.validateSubscription(idToken) + State.Authenticated(hasSubscription) + } catch (e: Throwable) { + State.Error(e) + } + } ?: State.Unauthenticated } else { state } From fdf9f5f5875d8a01a4cb6888e2a7c2768cee54a8 Mon Sep 17 00:00:00 2001 From: slawomirzaba Date: Tue, 4 Feb 2025 14:48:49 +0100 Subject: [PATCH 2/3] Update docs --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index e630e95..f5b92e9 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,19 @@ Any registered `Observer` will be called with the final authentication and subsc * We refresh these tokens automatically in the background before they're invalidated. * The subscription information gets validated as well on every token refresh. +### Recovering from network errors + +Sometimes we encounter an error state while refreshing the tokens in the background due to bad or no internet connection. + +You will notice this because the `state` switched to `Error`. This state object contains a reference to the original exception that was thrown. + +Since we don't monitor the device's connection state you need to tell the SDK that the network connection has been reestablished / improved. We will then refresh and revalidate the user's authentication tokens. + +```kotlin +contentPass.recoverFromError() +``` + + ### Counting an impression To count an impression, call either the suspending function `countImpressionSuspending(context: Context)` or the compatibility function `countImpression(context: Context, callback: CountImpressionCallback)`. From cff74831081ee8df54d7f1ee474cfaf9f1aa773b Mon Sep 17 00:00:00 2001 From: slawomirzaba Date: Tue, 4 Feb 2025 14:46:14 +0100 Subject: [PATCH 3/3] Bump version to 2.2.1 --- README.md | 2 +- lib/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f5b92e9..0621a1f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Our SDK is available on Maven Central. ```groovy -implementation 'de.contentpass:contentpass-android:2.1.1' +implementation 'de.contentpass:contentpass-android:2.2.1' ``` Add this to your app's `build.gradle` file's `dependencies` element. diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index e22b16d..abd221b 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -58,7 +58,7 @@ kapt { extra.apply{ set("PUBLISH_GROUP_ID", "de.contentpass") set("PUBLISH_ARTIFACT_ID", "contentpass-android") - set("PUBLISH_VERSION", "2.1.1") + set("PUBLISH_VERSION", "2.2.1") } apply("${rootProject.projectDir}/scripts/publish-module.gradle")