Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)`.
Expand Down
2 changes: 1 addition & 1 deletion lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
6 changes: 5 additions & 1 deletion lib/src/main/java/de/contentpass/lib/Authorizer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
44 changes: 38 additions & 6 deletions lib/src/main/java/de/contentpass/lib/ContentPass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -177,12 +187,15 @@ class ContentPass internal constructor(

private val observers = mutableListOf<Observer>()

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)
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
Expand Down