Android SDK Development
The Android SDK lives in android-sdks, a Kotlin library split into a platform layer and a Compose component library.
src/main/kotlin/dev/thunderid/
android/ Core SDK
auth/ PKCE and flow execution
http/ HTTP client
token/ Token store, validator, refresher, JWKS cache
compose/ Jetpack Compose component library
components/
actions/ SignInButton, SignOutButton, SignUpButton
guards/ SignedIn, SignedOut, Loading
presentation/ SignIn, SignUp, user and organization components
i18n/ Localization
src/test/kotlin/ Unit tests
samples/quickstart/ Demo app, not part of the SDK
tests/e2e/ End-to-end suite
The layering runs core → Compose → sample. The sample depends on the SDK through includeBuild, so a change to the SDK is picked up when you rebuild the sample.
Every Compose component ships in two variants: a styled one with opinionated Material 3 defaults, such as SignInButton, and a base slot-based one for full customization, such as BaseSignInButton.
Commands
./gradlew build
./gradlew test
./gradlew lint ktlintCheck
Build and install the sample onto a running emulator with ./gradlew installDebug from samples/quickstart. That directory is its own Gradle build, so ktlintCheck does not exist there; run lint from the repository root.
Code Style
- Kotlin targeting JVM 17.
minSdk26 for the SDK, 24 for the sample. - Jetpack Compose via BOM
2024.02.00. - No dependency injection framework. State reaches components through
CompositionLocal(LocalThunderID). - Coroutines (
suspendandFlow) rather than callbacks. - No third-party networking.
HttpClientwrapsHttpURLConnection. - Token storage uses
EncryptedSharedPreferencesbehind theStorageAdapterinterface. - Errors use the typed
IAMErrorCodeenum andIAMException.
ktlintCheck gates CI and every violation fails the build. The rules worth remembering are a 120-character line limit, alphabetically sorted imports with no wildcards, trailing commas on multi-line declarations but not at call sites, and } else { on one line.
Vendor Naming
The SDK is white-labelable through ThunderIDConfig.vendor. Do not hardcode thunderid in a runtime name that override should control, such as an EncryptedSharedPreferences key or a log tag. Resolve it from config.vendor instead.
An entry point whose purpose is to represent the SDK itself, such as ThunderIDClient, is a fixed identity and keeps the name.