Skip to content

Commit 9ae3ef9

Browse files
author
WarpLink
committed
docs: add documentation and tests
1 parent 7b6b110 commit 9ae3ef9

23 files changed

Lines changed: 3302 additions & 6 deletions

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024-2026 WarpLink (app.warplink)
3+
Copyright (c) 2026 WarpLink
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# WarpLink Android SDK
22

3-
[![CI](https://github.com/AplnkTo/warplink-android-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/AplnkTo/warplink-android-sdk/actions/workflows/ci.yml)
3+
[![CI](https://github.com/WarpLinkApp/warplink-android-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/WarpLinkApp/warplink-android-sdk/actions/workflows/ci.yml)
44
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
55

66
Deep linking SDK for Android. Handle App Links, deferred deep links, and install attribution with zero third-party dependencies.

docs/api-reference.md

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# API Reference
2+
3+
Complete reference for all public types and methods in the WarpLink Android SDK.
4+
5+
## WarpLink
6+
7+
The main entry point for the SDK. All methods are accessed via the singleton object.
8+
9+
```kotlin
10+
object WarpLink
11+
```
12+
13+
### Properties
14+
15+
#### `SDK_VERSION`
16+
17+
```kotlin
18+
const val SDK_VERSION: String // "0.1.0"
19+
```
20+
21+
The current SDK version string.
22+
23+
#### `isConfigured`
24+
25+
```kotlin
26+
val isConfigured: Boolean
27+
```
28+
29+
Whether the SDK has been configured via `configure()`. Thread-safe.
30+
31+
### Methods
32+
33+
#### `configure(context, apiKey, options)`
34+
35+
```kotlin
36+
fun configure(
37+
context: Context,
38+
apiKey: String,
39+
options: WarpLinkOptions = WarpLinkOptions()
40+
)
41+
```
42+
43+
Configure the SDK with your API key. Must be called before any other SDK methods.
44+
45+
**Parameters:**
46+
47+
| Parameter | Type | Description |
48+
|-----------|------|-------------|
49+
| `context` | `Context` | Android context (typically `Application`). The SDK retains `applicationContext` internally. |
50+
| `apiKey` | `String` | Your WarpLink API key (e.g., `wl_live_xxx...`). Must match the format `wl_live_` or `wl_test_` followed by 32 alphanumeric characters. |
51+
| `options` | `WarpLinkOptions` | Configuration overrides. Defaults to `WarpLinkOptions()`. |
52+
53+
**Throws:**
54+
55+
- `WarpLinkError.InvalidApiKeyFormat` if the API key doesn't match the expected format.
56+
57+
**Behavior:**
58+
- Validates API key format locally. Throws on invalid format (unlike iOS, which silently returns).
59+
- On valid format, initializes internal components and performs async server-side API key validation via `/sdk/validate`.
60+
- Server validation result is cached for 24 hours to avoid repeated network calls.
61+
62+
**Example:**
63+
64+
```kotlin
65+
// Basic configuration
66+
WarpLink.configure(
67+
context = this,
68+
apiKey = "wl_live_abcdefghijklmnopqrstuvwxyz012345"
69+
)
70+
71+
// With options
72+
WarpLink.configure(
73+
context = this,
74+
apiKey = "wl_live_abcdefghijklmnopqrstuvwxyz012345",
75+
options = WarpLinkOptions(debugLogging = true)
76+
)
77+
```
78+
79+
---
80+
81+
#### `handleDeepLink(uri, callback)`
82+
83+
```kotlin
84+
fun handleDeepLink(
85+
uri: Uri,
86+
callback: (Result<WarpLinkDeepLink>) -> Unit
87+
)
88+
```
89+
90+
Handle an incoming App Link URI and resolve it to a deep link.
91+
92+
**Parameters:**
93+
94+
| Parameter | Type | Description |
95+
|-----------|------|-------------|
96+
| `uri` | `Uri` | The App Link URI received by the Activity (from `intent?.data`). |
97+
| `callback` | `(Result<WarpLinkDeepLink>) -> Unit` | Called with the resolved deep link or an error. **Always called on the main thread.** |
98+
99+
**Errors (returned via `Result.failure`):**
100+
- `WarpLinkError.NotConfigured` — SDK not configured yet
101+
- `WarpLinkError.InvalidUrl` — URI is not a recognized WarpLink domain (`aplnk.to`)
102+
- `WarpLinkError.LinkNotFound` — Link does not exist or is inactive
103+
- `WarpLinkError.NetworkError(cause)` — Network request failed
104+
- `WarpLinkError.ServerError(statusCode, message)` — API returned an error
105+
- `WarpLinkError.InvalidApiKey` — API key rejected by server
106+
- `WarpLinkError.DecodingError(cause)` — Response parsing failed
107+
108+
**Example:**
109+
110+
```kotlin
111+
// In Activity
112+
intent?.data?.let { uri ->
113+
WarpLink.handleDeepLink(uri) { result ->
114+
result.onSuccess { deepLink ->
115+
Log.d("MyApp", "Link ID: ${deepLink.linkId}")
116+
Log.d("MyApp", "Destination: ${deepLink.destination}")
117+
deepLink.deepLinkUrl?.let { url ->
118+
Log.d("MyApp", "Deep link URL: $url")
119+
}
120+
}.onFailure { error ->
121+
Log.e("MyApp", "Error: ${error.message}")
122+
}
123+
}
124+
}
125+
```
126+
127+
---
128+
129+
#### `checkDeferredDeepLink(callback)`
130+
131+
```kotlin
132+
fun checkDeferredDeepLink(
133+
callback: (Result<WarpLinkDeepLink?>) -> Unit
134+
)
135+
```
136+
137+
Check for a deferred deep link on first launch. Returns `null` in the success case if no match was found.
138+
139+
**Parameters:**
140+
141+
| Parameter | Type | Description |
142+
|-----------|------|-------------|
143+
| `callback` | `(Result<WarpLinkDeepLink?>) -> Unit` | Called with the matched deep link (or `null` if no match), or an error. **Always called on the main thread.** |
144+
145+
**Behavior:**
146+
- On first launch: reads Play Install Referrer (deterministic), then falls back to fingerprint matching (probabilistic). Sends device signals to the attribution API and returns the match result.
147+
- On subsequent launches: returns the cached result from SharedPreferences without a network call.
148+
- The matched deep link has `isDeferred = true` and includes `matchType` and `matchConfidence`.
149+
150+
**Errors (returned via `Result.failure`):**
151+
- `WarpLinkError.NotConfigured` — SDK not configured yet
152+
- `WarpLinkError.NetworkError(cause)` — Network request failed
153+
- `WarpLinkError.ServerError(statusCode, message)` — API returned an error
154+
- `WarpLinkError.InvalidApiKey` — API key rejected by server
155+
- `WarpLinkError.DecodingError(cause)` — Response parsing failed
156+
157+
**Example:**
158+
159+
```kotlin
160+
WarpLink.checkDeferredDeepLink { result ->
161+
result.onSuccess { deepLink ->
162+
if (deepLink != null) {
163+
Log.d("MyApp", "Deferred match: ${deepLink.destination}")
164+
Log.d("MyApp", "Confidence: ${deepLink.matchConfidence}")
165+
} else {
166+
Log.d("MyApp", "No deferred deep link")
167+
}
168+
}.onFailure { error ->
169+
Log.e("MyApp", "Error: ${error.message}")
170+
}
171+
}
172+
```
173+
174+
---
175+
176+
## WarpLinkOptions
177+
178+
Configuration options for the SDK.
179+
180+
```kotlin
181+
data class WarpLinkOptions(
182+
val apiEndpoint: String = "https://api.warplink.app/v1",
183+
val debugLogging: Boolean = false,
184+
val matchWindowHours: Int = 72
185+
)
186+
```
187+
188+
### Properties
189+
190+
| Property | Type | Default | Description |
191+
|----------|------|---------|-------------|
192+
| `apiEndpoint` | `String` | `"https://api.warplink.app/v1"` | The API endpoint URL. Override for testing or custom deployments. |
193+
| `debugLogging` | `Boolean` | `false` | Enable debug logging with `WarpLink` tag in Logcat. |
194+
| `matchWindowHours` | `Int` | `72` | The match window in hours for deferred deep link attribution. |
195+
196+
**Example:**
197+
198+
```kotlin
199+
// Default options
200+
val options = WarpLinkOptions()
201+
202+
// Custom options
203+
val options = WarpLinkOptions(
204+
debugLogging = true,
205+
matchWindowHours = 48
206+
)
207+
208+
WarpLink.configure(context = this, apiKey = "wl_live_...", options = options)
209+
```
210+
211+
---
212+
213+
## WarpLinkDeepLink
214+
215+
Resolved deep link data returned by the SDK.
216+
217+
```kotlin
218+
data class WarpLinkDeepLink(
219+
val linkId: String,
220+
val destination: String,
221+
val deepLinkUrl: String? = null,
222+
val customParams: Map<String, Any> = emptyMap(),
223+
val isDeferred: Boolean = false,
224+
val matchType: MatchType? = null,
225+
val matchConfidence: Double? = null
226+
)
227+
```
228+
229+
### Properties
230+
231+
| Property | Type | Description |
232+
|----------|------|-------------|
233+
| `linkId` | `String` | The unique identifier of the link. |
234+
| `destination` | `String` | The resolved destination URL. |
235+
| `deepLinkUrl` | `String?` | The Android-specific deep link URL (e.g., `myapp://path`), if configured on the link. |
236+
| `customParams` | `Map<String, Any>` | Custom parameters attached to the link. See note below. |
237+
| `isDeferred` | `Boolean` | Whether this deep link was resolved via deferred attribution. |
238+
| `matchType` | `MatchType?` | The type of attribution match (`DETERMINISTIC` or `PROBABILISTIC`). |
239+
| `matchConfidence` | `Double?` | The confidence score of the attribution match (0.0 to 1.0). |
240+
241+
### Working with `customParams`
242+
243+
`customParams` is typed as `Map<String, Any>` because link parameters can contain mixed types (strings, numbers, booleans, nested objects).
244+
245+
Use safe casting when accessing values:
246+
247+
```kotlin
248+
WarpLink.handleDeepLink(uri) { result ->
249+
result.onSuccess { deepLink ->
250+
// Safe casting for custom parameters
251+
val productId = deepLink.customParams["product_id"] as? String
252+
productId?.let { showProduct(it) }
253+
254+
val discount = deepLink.customParams["discount"] as? Double
255+
discount?.let { applyDiscount(it) }
256+
}
257+
}
258+
```
259+
260+
---
261+
262+
## MatchType
263+
264+
The type of attribution match used to resolve a deferred deep link.
265+
266+
```kotlin
267+
enum class MatchType {
268+
DETERMINISTIC,
269+
PROBABILISTIC
270+
}
271+
```
272+
273+
### Values
274+
275+
| Value | Description |
276+
|-------|-------------|
277+
| `DETERMINISTIC` | Matched via Play Install Referrer. Confidence is always 1.0. |
278+
| `PROBABILISTIC` | Matched via enriched fingerprint. Confidence varies by time window (0.40–0.85). |
279+
280+
See [Attribution](attribution.md) for details on confidence scores.
281+
282+
---
283+
284+
## WarpLinkError
285+
286+
Typed errors for all SDK operations. Extends `Exception`.
287+
288+
```kotlin
289+
sealed class WarpLinkError(
290+
message: String,
291+
cause: Throwable? = null
292+
) : Exception(message, cause)
293+
```
294+
295+
### Subclasses
296+
297+
| Subclass | Description |
298+
|----------|-------------|
299+
| `NotConfigured` | SDK used before `configure()` was called. |
300+
| `InvalidApiKeyFormat` | API key format is invalid (must be `wl_live_` or `wl_test_` + 32 alphanumeric characters). **Thrown by `configure()`.** |
301+
| `InvalidApiKey` | API key was rejected by the server (revoked or incorrect). |
302+
| `NetworkError(cause: Throwable)` | Network request failed with an underlying cause. |
303+
| `ServerError(statusCode: Int, message: String)` | API returned an error response. |
304+
| `InvalidUrl` | The URI is not a valid WarpLink App Link (not an `aplnk.to` domain). |
305+
| `LinkNotFound` | The link was not found (404) or is no longer active. |
306+
| `DecodingError(cause: Throwable)` | Response parsing failed. |
307+
308+
Use exhaustive `when` for handling:
309+
310+
```kotlin
311+
result.onFailure { error ->
312+
when (error) {
313+
is WarpLinkError.NotConfigured -> { /* Call configure() first */ }
314+
is WarpLinkError.InvalidApiKeyFormat -> { /* Check key format */ }
315+
is WarpLinkError.InvalidApiKey -> { /* Regenerate key */ }
316+
is WarpLinkError.NetworkError -> { /* Retry with backoff */ }
317+
is WarpLinkError.ServerError -> { /* Log statusCode and message */ }
318+
is WarpLinkError.InvalidUrl -> { /* Not a WarpLink URL */ }
319+
is WarpLinkError.LinkNotFound -> { /* Check link in dashboard */ }
320+
is WarpLinkError.DecodingError -> { /* Update SDK */ }
321+
else -> { /* Unknown error */ }
322+
}
323+
}
324+
```
325+
326+
See [Error Handling](error-handling.md) for recommended recovery actions for each case.
327+
328+
## Thread Safety
329+
330+
- `WarpLink.isConfigured` is thread-safe (protected by `synchronized` lock).
331+
- All callbacks (`handleDeepLink`, `checkDeferredDeepLink`) are dispatched to the **main thread**.
332+
- `configure()` can be called from any thread, but should be called once during app initialization.

0 commit comments

Comments
 (0)