Skip to main content

Sinew

Concept​

Sinews connect muscle to bone. Sinew connects an app's layers: data (API responses, local storage) → domain (plain models, paging rules) → presentation (view state). It's a layered app architecture, shipped as small libraries an app adds one by one, so new apps stop rewriting response mapping, paging, error types, the network client with token refresh, and secure storage.

One sentence: Sinew ships the mechanism; the app ships the configuration.

It pairs with Camouflage:

  • Camouflage is the skin (UI); Sinew is what holds the app together underneath.
  • The two never depend on each other (Camouflage ADR-27, ADR-29). Only the optional sinew_camouflage glue knows both.
  • Sinew targets Flutter and Kotlin (Kotlin Multiplatform, usable from a plain Android app). Both get the same concepts, with names that follow each platform's tools.

The layers​

  • Envelopes that map themselves. Each envelope names its DTO type and its domain type. The compiler then knows every item has a toDomain(), so the envelope maps the payload, including every item of a list or page. No repository writes a mapper function.
  • Errors caught where they happen. Each layer's guard turns failures into a typed AppException with a module-layer-function-type code (ORD-R-GO-NIC), and hands a Result upward. View models never try/catch.
  • Presentation: page, event, action, effect, state.
    • A page sends events.
    • The view model maps each event to an action (mapEventToAction) and handles it (onAction).
    • It updates one immutable state (setState) and emits one-off effects (navigation, snackbars) through an EffectEmitter.
  • State-driven paging. A Pager<T> owns the paging rules. A list page only says how to fetch one page.

Packages​

v1​

PackageHolds
sinew_modelsDomain, Response, Entity, Envelope and four abstract envelope bases, PagingDomain, ViewState; on Flutter also Result
sinew_exceptionAppException and its codes, handler lists, the guards, CrashReporter
sinew_pagingPager<T>, PagingState<T>, LoadType, four paging strategies
sinew_presentationthe Event → Action → State + Effect base class, EffectEmitter (framework-neutral)
sinew_riverpod / sinew-viewmodelframework adapters (mixins and hooks / ViewModel bases)
sinew_riverpod_providersready Riverpod providers for every Sinew service
sinew_networkprocessApiCall, the auth layer (single-flight token refresh), timeout and redacting-log layers, network checker, retry and polling
sinew_storageSecureStore, KeyValueStore, processStorageCall, the DAO paging helper
sinew_securityFieldCipher (Tink AEAD), BiometricVault. No hand-rolled cryptography.
sinew_testingfakes, mock HTTP setup, test helpers for paging races
sinew_camouflagePagingState<T>.toCamo() → CamoPagedListState<T>
sinew_l10nEnglish and Indonesian translations for every built-in local error key (Flutter LocalizationsDelegate + ARB, Kotlin Compose resources)

1.1: DevTools​

PackageHolds
sinew_devtoolsthe inspector contract, recorders, redaction, fault injection, host override (no UI)
sinew_devtools_uioverlay button, dashboard and built-in inspectors on each platform's Material toolkit
sinew-devtools-noop (Kotlin)the same API as empty stubs, for release builds

Later​

Permission, notification, extensions (toMoney, toRelative), devtools inspectors that need native code (EXIF, camera, map), router adapters, a Camouflage skin/theme inspector, and an external devtools companion.

Naming: pub packages are sinew_*. Maven coordinates are com.srctool.sinew:sinew-*, with a sinew-bom. Every package is published on its own and released in lockstep. There's no umbrella package.

Mechanism vs. configuration​

Sinew shipsThe app supplies
Abstract envelope bases that map payloadsDTOs, and its own envelopes (fields, JSON keys, page metadata)
Guards and handler listsmodule and function codes
The HTTP client factory and its layersbase URLs, one client per backend, SinewConfig
The auth layerTokenSource (where tokens live, how to refresh)
Store interfaces and implementationstyped named stores (OrderPreferences)
The CrashReporter contract (no-op default)the real reporter
Adapters for Riverpod and ViewModelits own DI wiring

Doc map​

base/ holds the platform-neutral contracts. kotlin/ and flutter/ mirror it by path: kotlin/02 - Packages/03 - Paging implements base/02 - Packages/03 - Paging.

Foundations

  • Architecture: layers, the dependency graph, mechanism vs. configuration, publishing.
  • Error Model: codes, layers, handler lists, cancellation, CrashReporter.

Packages

Later (contract sketches)

Project

Platforms

  • Kotlin, Flutter.
  • Each also has 05 - Delivery/ (project setup, publishing, testing and CI), which has no base counterpart.
  • 03 - Later/ and 04 - Project/ have no platform counterparts.

Reading order:

  1. Architecture, then Error Model.
  2. The packages in number order. Each depends only on earlier ones.
  3. DevTools last.

Done when​

  • A sample app on each platform uses Sinew packages, and one list screen runs through sinew_camouflage into CamoPagedList (milestone S5).
  • 1.0 is published in lockstep on pub.dev and Maven Central (S6). DevTools ships as 1.1 (S7).

Edge cases​

CaseDecided behavior
An app uses Sinew without CamouflageSupported: no Sinew package depends on Camouflage except the optional glue.
An app uses Camouflage without SinewSupported: CamoPagedList takes a plain state. Any source works through a small adapter.
An app wants only pagingSupported: it adds sinew_paging, which brings sinew_models and sinew_exception, and nothing else.
The backend wraps payloads differentlyExpected: every app declares its own envelopes on Sinew's abstract bases. The guards accept any envelope.

Implementation​

This folder turns the platform-neutral design in Sinew into Kotlin Multiplatform code. It mirrors base/ by path: kotlin/02 - Packages/03 - Paging implements base/02 - Packages/03 - Paging. Every note keeps the base names and meaning, and adds only what Kotlin needs: real signatures, coroutines, expect/actual where a platform really differs, and Gradle.

Toolchain (checked 2026-10-02; re-check at S0)​

ToolVersionUsed for
Kotlin2.4.20the language and Gradle plugin (the same toolchain as Camouflage)
Android Gradle Plugin9.4.1 (Gradle 9.8.1 in the wrapper, JDK 17+)com.android.kotlin.multiplatform.library for every library module
Compose Multiplatform1.12.1 (Material 3 is versioned separately: 1.9.0)only in the modules that need UI or resources: sinew-viewmodel (ObserveEffects), sinew-l10n, sinew-camouflage, sinew-devtools-ui
kotlinx.coroutines1.11.0everywhere
kotlinx.serialization1.11.0 (stable; 1.12 is in RC)the network module's JSON
Ktor3.6.0sinew-network
AndroidX Lifecycle (multiplatform, JetBrains build)2.11.0ViewModel in sinew-viewmodel
AndroidX DataStore1.2.1KeyValueStore and the secure store's ciphertext on Android, iOS and JVM
Tink1.23.0FieldCipher and the secure store on Android and the JVM
cryptography-kotlin0.6.0the candidate for AES-GCM on iOS and web (see Security)
AndroidX Biometric1.1.0BiometricVault on Android
kotlinx-datetime0.8.0timestamps in records and retryAfter dates
Turbine1.2.1Flow assertions in tests
Kover0.9.11coverage
vanniktech Maven publish0.37.0publishing through the Central Portal

Repository and modules​

The code lives in sinew-kotlin, the Kotlin submodule of the umbrella repo srctool/sinew.

sinew-kotlin/
├── build-logic/ # the srctool convention plugins (shared with Camouflage)
├── gradle/*.versions.toml # split version catalogs
├── sinew-models/ sinew-exception/ sinew-l10n/
├── sinew-paging/ sinew-presentation/ sinew-viewmodel/
├── sinew-network/ sinew-security/ sinew-storage/
├── sinew-testing/ sinew-camouflage/
├── sinew-devtools/ sinew-devtools-ui/ sinew-devtools-noop/
├── sinew-bom/
└── sample/
├── shared/ # KMP: the sample list screen (commonMain) + desktop and web entry points
├── androidApp/
└── iosApp/ # Xcode project, a thin entry point

Package root: com.srctool.sinew.<module> (com.srctool.sinew.paging, com.srctool.sinew.network). Maven coordinates: com.srctool.sinew:sinew-<module>.

Base → Kotlin map​

BaseKotlin
ArchitectureArchitecture (api vs implementation, Koin wiring)
Error ModelError Model (kotlin.Result, cancellation)
ModelsModels
ExceptionException (+ sinew-l10n)
PagingPaging
PresentationPresentation
AdaptersAdapters (sinew-viewmodel)
NetworkNetwork
SecuritySecurity
StorageStorage
TestingTesting
Camouflage GlueCamouflage Glue
DevToolsDevTools
(none)Project Setup, Publishing, Testing and CI

base/03 - Later and base/04 - Project have no Kotlin notes. 05 - Delivery has no base note.

Kotlin-wide conventions for Sinew​

  1. explicitApi() in every library module: every public declaration says public, so the API surface is deliberate and the ABI dump readable.
  2. commonMain first. expect/actual only where a platform really differs: the HTTP engine, secure storage, crypto keys, biometrics, the network checker, uncaught-error hooks.
  3. Suspend functions, not callbacks. Repository-facing APIs are suspend. Streams are Flow. A long-running operation runs in the caller's scope.
  4. kotlin.Result everywhere, built only by Sinew's guards. runCatching is never used around suspend calls, because it swallows CancellationException.
  5. Hand-written sealed types, no code generation. ViewState, PagingState, ErrorMessage and the exception types are sealed classes or interfaces.
  6. No DI in libraries. Constructors and factories only. Koin wiring is an app concern, shown in the Architecture note.
  7. Compose only where it's needed. The four modules listed in the toolchain table. Everything else is plain Kotlin and works in a non-Compose app.