Skip to content

Commit deafac3

Browse files
authored
Merge pull request #9 from contentpass/CHORE-Improve-error-handling-in-SDK
Improve error handling in sdk
2 parents 3a486f1 + cff7483 commit deafac3

File tree

4 files changed

+58
-9
lines changed

4 files changed

+58
-9
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
Our SDK is available on Maven Central.
1515

1616
```groovy
17-
implementation 'de.contentpass:contentpass-android:2.1.1'
17+
implementation 'de.contentpass:contentpass-android:2.2.1'
1818
```
1919

2020
Add this to your app's `build.gradle` file's `dependencies` element.
@@ -149,6 +149,19 @@ Any registered `Observer` will be called with the final authentication and subsc
149149
* We refresh these tokens automatically in the background before they're invalidated.
150150
* The subscription information gets validated as well on every token refresh.
151151

152+
### Recovering from network errors
153+
154+
Sometimes we encounter an error state while refreshing the tokens in the background due to bad or no internet connection.
155+
156+
You will notice this because the `state` switched to `Error`. This state object contains a reference to the original exception that was thrown.
157+
158+
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.
159+
160+
```kotlin
161+
contentPass.recoverFromError()
162+
```
163+
164+
152165
### Counting an impression
153166
To count an impression, call either the suspending function `countImpressionSuspending(context: Context)` or
154167
the compatibility function `countImpression(context: Context, callback: CountImpressionCallback)`.

lib/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ kapt {
5858
extra.apply{
5959
set("PUBLISH_GROUP_ID", "de.contentpass")
6060
set("PUBLISH_ARTIFACT_ID", "contentpass-android")
61-
set("PUBLISH_VERSION", "2.1.1")
61+
set("PUBLISH_VERSION", "2.2.1")
6262
}
6363

6464
apply("${rootProject.projectDir}/scripts/publish-module.gradle")

lib/src/main/java/de/contentpass/lib/Authorizer.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ internal class Authorizer(
5151
init {
5252
val context = Dispatchers.Default + Job()
5353
CoroutineScope(context).launch {
54-
fetchConfig()
54+
try {
55+
fetchConfig()
56+
} catch (e: Throwable) {
57+
// this is allowed to fail
58+
}
5559
}
5660
}
5761

lib/src/main/java/de/contentpass/lib/ContentPass.kt

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,23 @@ class ContentPass internal constructor(
136136
*/
137137
sealed class State {
138138
/**
139-
* The contentpass object was just created. Will switch to another state very soon.
139+
* The contentpass object was just created or an error recovery is ongoing.
140+
* Will switch to another state very soon.
140141
*
141142
* After the stored contentpass token information is validated and refreshed, this will
142-
* switch to either [Unauthenticated] or [Authenticated]
143+
* switch to one of [Error], [Unauthenticated] or [Authenticated]
143144
*/
144145
object Initializing : State()
145146

147+
/**
148+
* An error was encountered during token validation.
149+
*
150+
* This is probably due to a failing internet connection. You can check the exception and
151+
* act accordingly. Once a stable network connection has been established, call [recoverFromError]
152+
* to retry the token validation.
153+
*/
154+
class Error(val exception: Throwable): State()
155+
146156
/**
147157
* No user is currently authenticated.
148158
*/
@@ -177,12 +187,15 @@ class ContentPass internal constructor(
177187

178188
private val observers = mutableListOf<Observer>()
179189

180-
private val coroutineContext = Dispatchers.Default + Job()
190+
private val coroutineContext = Dispatchers.Default + SupervisorJob()
181191

182192
init {
193+
initializeAuthState()
194+
}
195+
196+
private fun initializeAuthState() {
183197
tokenStore.retrieveAuthState()?.let {
184198
authState = it
185-
186199
CoroutineScope(coroutineContext).launch {
187200
onNewAuthState(authState)
188201
}
@@ -348,6 +361,19 @@ class ContentPass internal constructor(
348361
countSampledImpression()
349362
}
350363
}
364+
/**
365+
* Reinitializes this object's state.
366+
*
367+
* Call this function when you encountered an error during token validation, the current [state]
368+
* is set to [Error] and you want to try the validation again. Commonly used when network access
369+
* has been reestablished.
370+
*/
371+
fun recoverFromError() {
372+
state = State.Initializing
373+
374+
initializeAuthState()
375+
}
376+
351377

352378
private suspend fun countSampledImpression() {
353379
val generatedSample = Math.random()
@@ -405,8 +431,14 @@ class ContentPass internal constructor(
405431
state = if (authState.isAuthorized) {
406432
setupRefreshTimer(authState)?.let {
407433
if (it) {
408-
val hasSubscription = authorizer.validateSubscription(authState.idToken!!)
409-
State.Authenticated(hasSubscription)
434+
authState.idToken?.let { idToken ->
435+
try {
436+
val hasSubscription = authorizer.validateSubscription(idToken)
437+
State.Authenticated(hasSubscription)
438+
} catch (e: Throwable) {
439+
State.Error(e)
440+
}
441+
} ?: State.Unauthenticated
410442
} else {
411443
state
412444
}

0 commit comments

Comments
 (0)