Write values like 16.sdp and the library scales them from the current screen Configuration (size, density, optional flags).
New here? Use Quick start below, then GUIDE-FOR-BEGINNERS for every strategy in plain language.
Documentation: DOCUMENTATION/README.md · DOCUMENTATION/MODULES.md · KDoc (hosted) · PRD · PDR · Mathematics
3.1.7 keeps the modular packaging introduced in 3.1.6: the library ships as a principal artifact (common + core + scaled + plain) plus optional strategy modules. Kotlin packages and imports are unchanged.
dependencies {
implementation(platform("io.github.bodenberg:appdimens-dynamic-bom:3.1.7"))
implementation("io.github.bodenberg:appdimens-dynamic")
implementation("io.github.bodenberg:appdimens-dynamic-percent")
implementation("io.github.bodenberg:appdimens-dynamic-power")
implementation("io.github.bodenberg:appdimens-dynamic-fluid")
implementation("io.github.bodenberg:appdimens-dynamic-auto")
implementation("io.github.bodenberg:appdimens-dynamic-density")
implementation("io.github.bodenberg:appdimens-dynamic-diagonal")
implementation("io.github.bodenberg:appdimens-dynamic-fill")
implementation("io.github.bodenberg:appdimens-dynamic-fit")
implementation("io.github.bodenberg:appdimens-dynamic-interpolated")
implementation("io.github.bodenberg:appdimens-dynamic-logarithmic")
implementation("io.github.bodenberg:appdimens-dynamic-perimeter")
implementation("io.github.bodenberg:appdimens-dynamic-resize")
implementation("io.github.bodenberg:appdimens-dynamic-units")
}If you import com.appdimens.dynamic.compose.<strategy> (or code.<strategy>) without adding the matching artifact, the Gradle check checkAppDimensModules fails with a line such as:
Missing AppDimens module for import …percent… — add: implementation("io.github.bodenberg:appdimens-dynamic-percent:3.1.7")
Apply the same check in your app with:
apply(from = "<path-to-checkout>/gradle/appdimens-missing-module-check.gradle.kts")Runtime helper: com.appdimens.dynamic.core.MissingModule (package → Maven coordinate). Version comes from the appdimens.version Gradle property.
dependencies {
implementation("io.github.bodenberg:appdimens-dynamic:3.1.7")
implementation("io.github.bodenberg:appdimens-dynamic-percent:3.1.7")
// same satellites as above, each with :3.1.7
}| 3.1.5 | 3.1.6 |
|---|---|
One appdimens-dynamic dependency with every strategy |
Principal = scaled + core; declare each extra strategy module |
| — | Optional appdimens-dynamic-bom for shared version management |
| Kotlin imports | Unchanged |
No API, import, or packaging changes — 3.1.7 is an internal correctness and performance release; upgrade by bumping the version. Highlights of what changed under the hood:
- Persistent result cache removed.
DimenCacheno longer writes to Preferences DataStore. The in-memory cache is partitioned per window/configuration snapshot (DimenMetrics), so a rotated, resized, or recreated window can never observe values computed for another size, density, font scale, or multi-window state. - Exact aspect-ratio math. The hand-maintained binary-search lookup table in
AspectRatioLookupwas replaced by a deterministicln()computed once per snapshot — nearby screen ratios no longer collapse to the same approximated value. - Atomic cache entries. Key and value are published as a single immutable reference, eliminating the key/value race that could return another key’s value under concurrency.
- Per-window correctness for Compose.
AppDimensProviderprovidesLocalDimenMetrics; therememberDimen*helpers keep every dimension in a composition on the same coherent snapshot and remember on two keys instead of four. - Memory-leak fix. The
Context → Activityweak cache was removed. - Build/toolchain refresh: Kotlin 2.4.10, AGP 9.2.1, Compose BOM 2026.06.01, Material 1.14.0; CI uses least-privilege permissions and updated Actions; the Dokka output path is portable and git-ignored.
| Maven artifact | Contents |
|---|---|
appdimens-dynamic |
common, core, code.plain, code / compose scaled |
appdimens-dynamic-<strategy> |
code.<strategy> + compose.<strategy> |
appdimens-dynamic-bom |
Version constraints (java-platform) |
Module graph: DOCUMENTATION/MODULES.md.
Requirements: Min SDK 24 · Compile SDK 36 · Kotlin & Java 17 · Jetpack Compose
import com.appdimens.dynamic.compose.*
Box(
Modifier
.padding(16.sdp)
.width(100.wdp)
.height(48.hdp)
) {
Text("Hello", fontSize = 16.ssp)
}| Extension | Based on | Typical use |
|---|---|---|
sdp |
Smallest window width | Padding, margins |
hdp |
Screen height | Row height |
wdp |
Screen width | Column width |
ssp |
Same idea as sdp, for text |
fontSize |
sem |
Same idea as sdp, for text |
fontSize ignore system font scale |
If you only use sdp / hdp / wdp / ssp / hsp / wsp/ sem / hem / wem (and variants like sdpa), you can skip this block.
Use it when you call .sdpMode, .sdpScreen, .sspMode, .sspScreen, or similar facilitators that depend on UI mode / fold state. It sets LocalUiModeType once for the tree instead of resolving mode on every call, and (since 3.1.7) provides LocalDimenMetrics — a coherent per-window snapshot that every rememberDimen* helper uses.
import com.appdimens.dynamic.core.AppDimensProvider
setContent {
AppDimensProvider {
MyApp()
}
}Since 3.1.7 the cache is partitioned per window snapshot (DimenMetrics): every resolution is keyed by the exact configuration it was computed for, so a rotated, resized, or recreated window can never read a stale value. Explicit invalidation is therefore not required for correctness — this API is retained as a compatibility hook and no longer wipes other windows’ hot entries.
Call it when the same Activity stays alive across rotation, split-screen, or density/font changes and you want to refresh internal bookkeeping. If the Activity is recreated on config change (default), you don’t need it. Details: library/PERFORMANCE.md.
The previous Configuration is tracked internally by DimenCache — callers only need to pass the new one.
import com.appdimens.dynamic.core.DimenCache
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
DimenCache.invalidateOnConfigChange(newConfig)
}You only get onConfigurationChanged if the Activity lists android:configChanges for those changes in the manifest; otherwise the process usually recreates the Activity and config is fresh automatically.
| Suffix | Meaning |
|---|---|
| (none) | Default |
a |
Aspect ratio–aware curve |
i |
Ignore multi-window heuristic (may return unscaled base when it triggers) |
ia |
Both |
16.sdpa // + aspect ratio
32.hdpi // height axis + ignore multi-window
16.sspa // scalable sp + aspect ratioText("Scaled (sw)", fontSize = 16.ssp)
Text("Scaled (height)", fontSize = 20.hsp)
Text("Scaled (width)", fontSize = 18.wsp)
Text("No system font scale (sw)", fontSize = 16.sem) // sem / hem / wem32.sdpPh // SW-based; in portrait uses height
32.sdpLw // SW-based; in landscape uses width
50.hdpLw // Height-based; in landscape uses width
50.wdpLh // Width-based; in landscape uses heightimport com.appdimens.dynamic.compose.*
import com.appdimens.dynamic.common.DpQualifier
import com.appdimens.dynamic.common.Orientation
import com.appdimens.dynamic.common.UiModeType
80.sdpRotate(50, orientation = Orientation.LANDSCAPE)
30.sdpMode(200, UiModeType.TELEVISION)
60.sdpQualifier(120, DpQualifier.SMALL_WIDTH, 600)
16.sspRotate(24, orientation = Orientation.LANDSCAPE)Full catalog: DOCUMENTATION/COMPOSE-API-CONVENTIONS.md.
val pad = 16.scaledDp()
.aspectRatio(true)
.screen(UiModeType.TELEVISION, 40)
.screen(DpQualifier.SMALL_WIDTH, 600, 24)
.sdpPicks the largest font or size in a min…max range that still fits the space. Use for titles, squares, etc.
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.ui.Modifier
import com.appdimens.dynamic.compose.resize.autoResizeTextSp
BoxWithConstraints(Modifier.fillMaxWidth()) {
val fontSize = autoResizeTextSp(
text = "Headline that must fit",
minSp = 12,
maxSp = 28,
stepSp = 1,
maxLines = 2,
)
Text("Headline that must fit", fontSize = fontSize, maxLines = 2)
}More APIs (autoResizeSquareSize, ResizeBound, …): DOCUMENTATION/resize.md.
import com.appdimens.dynamic.code.DimenSdp
import com.appdimens.dynamic.code.DimenSsp
val paddingPx = DimenSdp.sdp(context, 16)
val heightPx = DimenSdp.hdp(context, 32)
val widthPx = DimenSdp.wdp(context, 100)
val fontPx = DimenSsp.ssp(context, 16)
// Extensions (see code package)
// 16.ssp(context), DimenSdp.scaled(16).screen(...).sdp(context), sdpRotate, …import com.appdimens.dynamic.code.DimenSdp;
import com.appdimens.dynamic.code.DimenScaled;
import com.appdimens.dynamic.code.DimenSsp;
import com.appdimens.dynamic.common.UiModeType;
float paddingPx = DimenSdp.sdp(context, 16);
float heightPx = DimenSdp.hdp(context, 32);
float fontPx = DimenSsp.ssp(context, 16);
DimenScaled scaled = DimenSdp.scaled(16)
.applyAspectRatio(true)
.screen(UiModeType.TELEVISION, 32);
float result = scaled.sdp(context);Approximate real-world size on screen (density-based). Compose: use helpers from the library and .dp on the result where needed — see DOCUMENTATION/physical-units.md. Code module: com.appdimens.dynamic.code.units.DimenPhysicalUnits (toDpFromMm, …).
Recommendation order for most apps: Scaled (with or without a) → then percent → then auto; explore the rest when you have a clear need (fluid, fit, diagonal, etc.).
Other strategies (percent, power, fluid, auto, diagonal, fill, fit, interpolated, logarithmic, perimeter, density, resize, units) mirror the Scaled suffix patterns under a different import prefix and ship as separate Maven modules. See DOCUMENTATION/MODULES.md, DOCUMENTATION/README.md, and GUIDE-FOR-BEGINNERS.
| Resource | Use for |
|---|---|
| DOCUMENTATION/README.md | Per-strategy explanations |
| DOCUMENTATION/MODULES.md | Gradle/Maven module graph (3.1.7) |
| COMPOSE-API-CONVENTIONS.md | Every Compose property & facilitator (scaled catalog + prefix map) |
| DOCUMENTATION/index.md | Markdown API index (KDoc export) |
| appdimens3.web.app | Searchable KDoc |
Example app: app/.../compose/ExampleActivity.kt (includes auto-resize demos).
- Results are cached in
DimenCache— lock-free, partitioned per window/configuration snapshot (no disk persistence since 3.1.7). - Some paths skip storing in the shard table when a cheap multiply is enough — see library/PERFORMANCE.md.
- Batch / low-level keys: not needed for normal app code; library extensions already use the cache.
Scaled uses 300 dp as the design reference. It is the most widely used strategy in real apps and the recommended default: use plain sdp / hdp / wdp / ssp when a single curve is enough, and the a suffix (aspect ratio–aware), e.g. 16.sdpa, when you want scaling tuned to screen shape. After Scaled, the next strategies teams typically adopt are percent (sizes as a fraction of an axis) and auto (breakpoint-style steps); the other modes are for specialized layouts — see DOCUMENTATION/README.md.
Facilitators — two “Plain” styles: *RotatePlain, *ModePlain, *QualifierPlain, *ScreenPlain (and *PlainPx) exist with the alternate as Number (active branch still runs through scaling/cache) or as Dp / TextUnit (only the condition is evaluated; no second scaling). For nested chains such as 30.sdp.sdpRotatePlain(20.sdp).sdpModePlain(40.sdp, UiModeType.TELEVISION), prefer Dp / TextUnit alternates so neither the receiver nor the alternate is scaled twice. Nesting order is the order you write the chain (outer → inner). That is different from DimenScaled .screen chains, where priority is defined inside the builder API, not by lexical nesting — see DOCUMENTATION/COMPOSE-API-CONVENTIONS.md.
Views / code: the same logic-only Plain branching exists on Float px + Context — Dimen*PlainPx.kt per strategy (e.g. psdpRotatePlainPx in com.appdimens.dynamic.code.percent), with shared helpers in com.appdimens.dynamic.code.plain (DimenPlainBranch.kt). Dp/Sp facilitator sources use the same Dimen<Strategy>DpExtensions.kt / Dimen<Strategy>SpExtensions.kt names as in compose/<strategy>/ (scaled: DimenSdpExtensions.kt / DimenSspExtensions.kt under code/scaled/). Details in DOCUMENTATION/COMPOSE-API-CONVENTIONS.md §4.5 and DOCUMENTATION/README.md.
- Code-only scaling (no XML dimen grids) · SDP / HDP / WDP + 14 scaling modes
- Aspect ratio & multi-window flags · Inverters & facilitators · Foldable awareness via WindowManager
- Physical units · Resize helpers · DimenScaled chains
- 3.1.7: per-window
DimenMetricssnapshots · atomic snapshot-partitioned cache · exactlnaspect-ratio math · persistence removed · multi-window / Compose correctness and leak fixes
Apache License 2.0 — responsive layout utilities for Android.


