Active Maven 1.0.3 com.soasap:soasap

SOASAP Kotlin SDK

Lightweight, production-ready feature flags SDK for Kotlin on the JVM and Android — lock-free O(1) reads, zero network calls on startup (unless you opt into preload), real-time SSE updates, persistent disk cache, graceful offline behavior, and minimal dependencies (OkHttp, coroutines, kotlinx.serialization).

Installation

Install from Maven Central. Requires JDK 17+. For getJson, apply the Kotlin serialization plugin in your consumer project.

// build.gradle.kts
dependencies {
    implementation("com.soasap:soasap:1.0.3")
}
<!-- pom.xml -->
<dependency>
    <groupId>com.soasap</groupId>
    <artifactId>soasap</artifactId>
    <version>1.0.3</version>
</dependency>

Quick start

Create a SoasapClient with SoasapOptions, set preloadFlags = true for non-blocking startup sync, and evaluate flags with synchronous getters. Always call close() or use .use { }.

import soasap.sdk.SoasapClient
import soasap.sdk.SoasapOptions

val options = SoasapOptions("your-api-key").apply {
    preloadFlags = true
    onError = { ctx ->
        println("[${ctx.source}] ${ctx.exception.message}")
    }
}

SoasapClient(options).use { client ->
    // use flags
}

Fluent builder (optional)

Optional DSL via soasap { } for the same options with a fluent API.

import soasap.sdk.soasap

soasap("your-api-key")
    .preloadFlags()
    .onError { ctx -> println(ctx.exception.message) }
    .build()
    .use { client ->
        if (client.getBool("new-checkout")) {
            println("New checkout enabled")
        }
    }

Use feature flags

Evaluate flags on the hot path with zero network or disk I/O. Getters never throw — safe for production request paths and Android UI threads.

SoasapClient(options).use { client ->
    if (client.getBool("new-checkout")) {
        println("New checkout enabled")
    } else {
        println("Old checkout")
    }
}

Typed access

Bool, number, string, and JSON remote config via kotlinx.serialization. Use @SerialName when dashboard keys differ from Kotlin property names.

val enabled = client.getBool("feature-x")
val limit   = client.getNumber("rate-limit", defaultValue = 100.0)
val theme   = client.getString("ui-theme", defaultValue = "light")
val config  = client.getJson<CheckoutConfig>("checkout-config")

Startup sync

preloadFlags = true (recommended) starts the SSE background worker right after construction. The constructor returns immediately; your app keeps booting even if the API is down.

Without preload, no background worker runs until the first flag read — the first evaluation may return defaults (or disk cache if present) while SSE connects in the background.

// Immediate sync (recommended)
val options = SoasapOptions("your-api-key").apply { preloadFlags = true }

// Lazy sync — first read falls back to defaults or disk cache
val options = SoasapOptions("your-api-key")

Ktor integration

Share one thread-safe SoasapClient instance across routes (for example, a DI singleton).

routing {
    get("/") {
        if (flags.getBool("maintenance-mode")) {
            call.respond(HttpStatusCode.ServiceUnavailable, "Maintenance")
            return@get
        }
        call.respondText("OK")
    }
}

Error handling & observability

Hook background diagnostics without affecting the hot path. Sources: Network, Disk, Parser.

SoasapOptions("your-api-key").apply {
    onError = { ctx ->
        println("[${ctx.source}] (transient=${ctx.isTransient}) -> ${ctx.exception.message}")
    }
}

// SoasapErrorSource.Network | .Disk | .Parser

Production safety & guardrails

The SDK never throws from flag getters. Reconnect backoff: 1s → 2s → 5s → 10s → 30s (±200ms jitter).

Offline resiliency

ScenarioBehavior
API unavailableUses stale cached flags
SSE disconnectedKeeps last known snapshot
First startup without cacheReturns default values
Invalid payloadPayload ignored
Disk cache failureIn-memory mode continues
Persistent network issuesAutomatic reconnect with backoff

Cache file: soasap_cache_.json (derived from API key).
Windows: %LOCALAPPDATA%\soasap\cache. Linux/macOS: ~/.local/share/soasap/cache.
Override with cacheDirectory = "/custom/path" on SoasapOptions.

Architecture

[Hot Path]   client.getBool() → AtomicReference.get(snapshot) → O(1) lookup
                              ↑
                              | (atomic snapshot swap)
[Background] SSE → SseEventParser (5MB cap) → DiskWriteCoalescer (debounce) → local disk

Supported environments

← All SDKs