POROROCA OTA · DOCUMENTATION
Jetpack Compose integration
A complete Android API 26+ verification, storage, and rendering path.
REQUIREMENTS
Include the pinned API 26+ source module.
Pin this repository checkout. Use the included Android workspace as the versioned dependency reference.
// settings.gradle.kts
include(":pororocaSdk")
project(":pororocaSdk").projectDir = file("../pororoca-ota/android/sdk")
// app/build.gradle.kts
dependencies { implementation(project(":pororocaSdk")) }
// verify the pinned checkout
// cd /path/to/pororoca-ota/android
// ./gradlew :sdk:testDebugUnitTest :app:assembleDebug
01 · CONFIGURATION
Decode the key and persist cohort identity.
fun publicKey(base64: String): ByteArray =
java.util.Base64.getDecoder().decode(base64).also {
require(it.size == 32) { "Ed25519 public key must be 32 bytes" }
}
fun installId(context: Context): String {
val prefs = context.getSharedPreferences("pororoca", Context.MODE_PRIVATE)
return prefs.getString("install-id", null) ?: UUID.randomUUID().toString().also {
prefs.edit().putString("install-id", it).apply()
}
}
Build constants are extractable. The mobile token must remain runtime-scoped and revocable.
02 · CLIENT
Resolve and verify away from the main thread.
val client = PororocaClient(
serverUrl = "https://pororoca-ota.fly.dev",
apiToken = BuildConfig.POROROCA_RUNTIME_TOKEN,
app = "your-app-slug", channel = "production",
installId = installId(applicationContext),
publicKey = publicKey(BuildConfig.POROROCA_PUBLIC_KEY),
)
val update = runCatching { client.checkForUpdate() }
.onFailure(appDiagnostics::record).getOrNull()
The client verifies signature, key ID, hashes, byte counts, paths, platform, and strict decoding before returning.
03 · OFFLINE STORAGE
Persist verified bytes atomically.
val current = File(context.filesDir, "pororoca/paywall.json")
update?.documentBytes?.get("paywall")?.let { bytes ->
current.parentFile?.mkdirs()
val pending = File(current.parentFile, "paywall.pending")
pending.writeBytes(bytes)
Files.move(pending.toPath(), current.toPath(),
StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
}
val document = update?.documents?.get("paywall")
?: runCatching { PororocaDocument.decode(current.readBytes()) }.getOrNull()
?: PororocaDocument.decode(EMBEDDED_PAYWALL.encodeToByteArray())
04 · RENDER AND REPORT
Keep behavior native.
PororocaScreen(document = document,
host = PororocaHost(
strings = mapOf("paywall.title" to stringResource(R.string.paywall_title)),
onAction = { action -> when (action) {
"purchase" -> launchBillingFlow()
"restore" -> restorePurchases()
"dismiss" -> navigator.goBack()
else -> appDiagnostics.record("Unknown Pororoca action: $action")
}}))
update?.let { client.record(event = "applied", updateId = it.id) }
Supported nodes: vstack, hstack, zstack, box, spacer, scroll, lazycolumn, text, divider, progress, and button. Full Swift modifier/expression parity is not yet available.
- Airplane mode renders retained or embedded content.
- A wrong key prevents adoption.
- Malformed documents fail closed.
- Every named action reaches Kotlin behavior.