
Building blocks for app architecture: keyed coroutine task management via TaskScope (launch/skip/replace/debounce by key), Flow Result triad, retry/backoff utilities, and encrypted DataStore.
Kotlin Multiplatform building blocks for the Criollo architecture.
This release focuses on keyed coroutine task management (TaskScope): launch, skip, replace, and
debounce side effects by key—without fire-and-forget launch calls. More infrastructure modules will
land in this monorepo over time.
Group: io.github.jdbenitez94.criollo.kmp.foundation
| Artifact | Maven name | Gradle project | Required? | Role |
|---|---|---|---|---|
| Bill of Materials (BOM) | bom |
:bom |
Recommended | Aligns versions of all foundation modules |
| Core | coroutines |
:coroutines |
Yes |
TaskScope registry (TaskKey, TaskPolicy, …) |
| ViewModel | coroutines-viewmodel |
:coroutines:viewmodel |
Optional |
by taskScope() on ViewModel
|
| Compose | coroutines-compose |
:coroutines:compose |
Optional |
rememberTaskScope() in Composables |
| Result | result |
:result |
Optional | Flow Result triad (Loading / Success / Error) + asResult()
|
| Runtime | runtime |
:runtime |
Optional |
retryWithBackoff (exponential backoff + jitter) |
| Tooling | project-conventions |
:project-conventions |
Optional | Gradle plugin to sync .editorconfig + Detekt configs |
| Crypto (KryptoStore) | kryptostore-crypto |
:kryptostore:crypto |
Optional | Platform crypto for encrypted DataStore (Tink / Keystore / WebCrypto) |
| Serializers (KryptoStore) | kryptostore-serializers |
:kryptostore:serializers |
Optional | Encrypted Okio envelope serializers + fail-closed corruption handler |
| Core (KryptoStore) | kryptostore |
:kryptostore |
Optional | Encrypted typed DataStore factories + IndexedDB storage |
| Preferences (KryptoStore) | kryptostore-preferences |
:kryptostore:preferences |
Optional | Encrypted + plain Preferences DataStore factories |
| Android DX (KryptoStore) | kryptostore-android-delegates |
:kryptostore:android |
Optional |
Context property delegates for encrypted/plain stores |
| Migrate Android (KryptoStore) | kryptostore-migrate-android |
:kryptostore:migrate-android |
Optional | Unenveloped AEAD migration helpers |
| Testing | testing |
:testing |
Optional | JUnit 5 helpers (MainTestDispatcherExtension) |
Only coroutines is required. Pick ViewModel and/or Compose adapters when you want the convenience
APIs; you can also construct TaskScope(coroutineScope) yourself.
Packages: …foundation.coroutines (+ .viewmodel / .compose);
…foundation.result; …foundation.runtime;
KryptoStore: …foundation.kryptostore
(+ .crypto / .serializers / .preferences / .android / .migrate);
testing: …foundation.testing.
dependencies {
// Version is bumped by release-please with each GitHub Release / Maven Central publish.
implementation(platform("io.github.jdbenitez94.criollo.kmp.foundation:bom:0.2.0")) // x-release-please-version
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines")
// Optional adapters — add what you use:
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines-viewmodel")
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines-compose")
}Pin the BOM to a release (see the Maven Central badge above for the latest). With the BOM on the
classpath, omit versions on the module lines. kotlinx-coroutines-core is exposed transitively
(api) from coroutines.
ViewModel (needs coroutines-viewmodel):
class SignInViewModel : ViewModel() {
private val tasks by taskScope()
fun onSubmit() {
tasks.launch(TaskKey.of("auth.sign_in"), TaskPolicy.SkipIfActive) {
// side effect
}
}
}Compose (needs coroutines-compose):
@Composable
fun SignInScreen() {
val tasks = rememberTaskScope()
Button(
onClick = {
tasks.launch(TaskKey.of("auth.sign_in"), TaskPolicy.SkipIfActive) {
// side effect
}
},
) { Text("Sign in") }
}Core only (no adapter artifacts):
val tasks = TaskScope(viewModelScope) // or any CoroutineScope
tasks.launch(TaskKey.of("sync.refresh"), TaskPolicy.ReplaceActive) {
// side effect
}dependencies {
implementation(platform("io.github.jdbenitez94.criollo.kmp.foundation:bom:0.2.0")) // x-release-please-version
implementation("io.github.jdbenitez94.criollo.kmp.foundation:result")
implementation("io.github.jdbenitez94.criollo.kmp.foundation:runtime")
}Flow triad:
items.asResult().collect { result ->
when (result) {
is Result.Loading -> Unit
is Result.Success -> render(result.data)
is Result.Error -> show(result.exception)
}
}Retry with backoff:
val payload = retryWithBackoff(RetryPolicy(maxAttempts = 3)) { attempt ->
api.fetch(attempt)
}Cancellation-safe runCatching:
val result = suspendRunCatchingCancellable { api.fetch() }
result.onFailureExceptCancellation { log(it) }dependencies {
implementation(platform("io.github.jdbenitez94.criollo.kmp.foundation:bom:0.2.0")) // x-release-please-version
implementation("io.github.jdbenitez94.criollo.kmp.foundation:kryptostore")
implementation("io.github.jdbenitez94.criollo.kmp.foundation:kryptostore-preferences")
// Android Context delegates (artifact id avoids clash with kryptostore's android KMP target):
implementation("io.github.jdbenitez94.criollo.kmp.foundation:kryptostore-android-delegates")
}JVM / Android / iOS (file): initialize crypto, then create an encrypted proto store:
val stack = createPlatformCryptoStack("my.app")
val runtime = CryptoRuntime(stack)
runtime.initialize() // Ready before use
val store = createEncryptedProtoDataStore(
cipher = runtime.cipher,
kSerializer = Settings.serializer(),
defaultValue = Settings(),
producePath = { path },
registry = runtime.registry,
)Android delegates:
val Context.settings by encryptedProtoDataStore(
fileName = "settings.pb",
kSerializer = Settings.serializer(),
defaultValue = Settings(),
cipher = { runtime.cipher },
registry = runtime.registry,
)Web: typed store → IndexedDB (createEncryptedProtoDataStoreIndexedDb); prefs → localStorage
(createEncryptedPreferencesDataStoreLocalStorage / plain variant). Keys stay in IndexedDB (app-crypto).
Migration guides: kryptostore-migration.md. Crypto notes: kryptostore-crypto.md.
Site (MkDocs + Dokka API HTML): jdbenitez94.github.io/criollo-kmp-foundation.
TaskScope) — policies, adapters, anti-patternswebpack.config.d fallbacks./gradlew qualityCheck jvmLibraryTestsRequires JDK 21 (Temurin) and an Android SDK (local.properties with sdk.dir).
Point the IDE Gradle JDK at Temurin 21 so -XX:+UseZGC works.
./gradlew installGitHooksSets core.hooksPath to gradle/hooks/ (pre-commit format/check, pre-push full quality + JVM tests).
qualityCheck runs installGitHooks automatically. Details: contributing.md.
Kotlin Multiplatform building blocks for the Criollo architecture.
This release focuses on keyed coroutine task management (TaskScope): launch, skip, replace, and
debounce side effects by key—without fire-and-forget launch calls. More infrastructure modules will
land in this monorepo over time.
Group: io.github.jdbenitez94.criollo.kmp.foundation
| Artifact | Maven name | Gradle project | Required? | Role |
|---|---|---|---|---|
| Bill of Materials (BOM) | bom |
:bom |
Recommended | Aligns versions of all foundation modules |
| Core | coroutines |
:coroutines |
Yes |
TaskScope registry (TaskKey, TaskPolicy, …) |
| ViewModel | coroutines-viewmodel |
:coroutines:viewmodel |
Optional |
by taskScope() on ViewModel
|
| Compose | coroutines-compose |
:coroutines:compose |
Optional |
rememberTaskScope() in Composables |
| Result | result |
:result |
Optional | Flow Result triad (Loading / Success / Error) + asResult()
|
| Runtime | runtime |
:runtime |
Optional |
retryWithBackoff (exponential backoff + jitter) |
| Tooling | project-conventions |
:project-conventions |
Optional | Gradle plugin to sync .editorconfig + Detekt configs |
| Crypto (KryptoStore) | kryptostore-crypto |
:kryptostore:crypto |
Optional | Platform crypto for encrypted DataStore (Tink / Keystore / WebCrypto) |
| Serializers (KryptoStore) | kryptostore-serializers |
:kryptostore:serializers |
Optional | Encrypted Okio envelope serializers + fail-closed corruption handler |
| Core (KryptoStore) | kryptostore |
:kryptostore |
Optional | Encrypted typed DataStore factories + IndexedDB storage |
| Preferences (KryptoStore) | kryptostore-preferences |
:kryptostore:preferences |
Optional | Encrypted + plain Preferences DataStore factories |
| Android DX (KryptoStore) | kryptostore-android-delegates |
:kryptostore:android |
Optional |
Context property delegates for encrypted/plain stores |
| Migrate Android (KryptoStore) | kryptostore-migrate-android |
:kryptostore:migrate-android |
Optional | Unenveloped AEAD migration helpers |
| Testing | testing |
:testing |
Optional | JUnit 5 helpers (MainTestDispatcherExtension) |
Only coroutines is required. Pick ViewModel and/or Compose adapters when you want the convenience
APIs; you can also construct TaskScope(coroutineScope) yourself.
Packages: …foundation.coroutines (+ .viewmodel / .compose);
…foundation.result; …foundation.runtime;
KryptoStore: …foundation.kryptostore
(+ .crypto / .serializers / .preferences / .android / .migrate);
testing: …foundation.testing.
dependencies {
// Version is bumped by release-please with each GitHub Release / Maven Central publish.
implementation(platform("io.github.jdbenitez94.criollo.kmp.foundation:bom:0.2.0")) // x-release-please-version
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines")
// Optional adapters — add what you use:
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines-viewmodel")
implementation("io.github.jdbenitez94.criollo.kmp.foundation:coroutines-compose")
}Pin the BOM to a release (see the Maven Central badge above for the latest). With the BOM on the
classpath, omit versions on the module lines. kotlinx-coroutines-core is exposed transitively
(api) from coroutines.
ViewModel (needs coroutines-viewmodel):
class SignInViewModel : ViewModel() {
private val tasks by taskScope()
fun onSubmit() {
tasks.launch(TaskKey.of("auth.sign_in"), TaskPolicy.SkipIfActive) {
// side effect
}
}
}Compose (needs coroutines-compose):
@Composable
fun SignInScreen() {
val tasks = rememberTaskScope()
Button(
onClick = {
tasks.launch(TaskKey.of("auth.sign_in"), TaskPolicy.SkipIfActive) {
// side effect
}
},
) { Text("Sign in") }
}Core only (no adapter artifacts):
val tasks = TaskScope(viewModelScope) // or any CoroutineScope
tasks.launch(TaskKey.of("sync.refresh"), TaskPolicy.ReplaceActive) {
// side effect
}dependencies {
implementation(platform("io.github.jdbenitez94.criollo.kmp.foundation:bom:0.2.0")) // x-release-please-version
implementation("io.github.jdbenitez94.criollo.kmp.foundation:result")
implementation("io.github.jdbenitez94.criollo.kmp.foundation:runtime")
}Flow triad:
items.asResult().collect { result ->
when (result) {
is Result.Loading -> Unit
is Result.Success -> render(result.data)
is Result.Error -> show(result.exception)
}
}Retry with backoff:
val payload = retryWithBackoff(RetryPolicy(maxAttempts = 3)) { attempt ->
api.fetch(attempt)
}Cancellation-safe runCatching:
val result = suspendRunCatchingCancellable { api.fetch() }
result.onFailureExceptCancellation { log(it) }dependencies {
implementation(platform("io.github.jdbenitez94.criollo.kmp.foundation:bom:0.2.0")) // x-release-please-version
implementation("io.github.jdbenitez94.criollo.kmp.foundation:kryptostore")
implementation("io.github.jdbenitez94.criollo.kmp.foundation:kryptostore-preferences")
// Android Context delegates (artifact id avoids clash with kryptostore's android KMP target):
implementation("io.github.jdbenitez94.criollo.kmp.foundation:kryptostore-android-delegates")
}JVM / Android / iOS (file): initialize crypto, then create an encrypted proto store:
val stack = createPlatformCryptoStack("my.app")
val runtime = CryptoRuntime(stack)
runtime.initialize() // Ready before use
val store = createEncryptedProtoDataStore(
cipher = runtime.cipher,
kSerializer = Settings.serializer(),
defaultValue = Settings(),
producePath = { path },
registry = runtime.registry,
)Android delegates:
val Context.settings by encryptedProtoDataStore(
fileName = "settings.pb",
kSerializer = Settings.serializer(),
defaultValue = Settings(),
cipher = { runtime.cipher },
registry = runtime.registry,
)Web: typed store → IndexedDB (createEncryptedProtoDataStoreIndexedDb); prefs → localStorage
(createEncryptedPreferencesDataStoreLocalStorage / plain variant). Keys stay in IndexedDB (app-crypto).
Migration guides: kryptostore-migration.md. Crypto notes: kryptostore-crypto.md.
Site (MkDocs + Dokka API HTML): jdbenitez94.github.io/criollo-kmp-foundation.
TaskScope) — policies, adapters, anti-patternswebpack.config.d fallbacks./gradlew qualityCheck jvmLibraryTestsRequires JDK 21 (Temurin) and an Android SDK (local.properties with sdk.dir).
Point the IDE Gradle JDK at Temurin 21 so -XX:+UseZGC works.
./gradlew installGitHooksSets core.hooksPath to gradle/hooks/ (pre-commit format/check, pre-push full quality + JVM tests).
qualityCheck runs installGitHooks automatically. Details: contributing.md.