01 · Installation
Two repositories, one plugin
Gradle resolves plugins and libraries from separate repository lists. Add the public
DebugInput Maven repository to both lists in the root settings.gradle.kts.
DebugInput latest requires Kotlin 2.4.20. The Gradle plugin fails early with a readable error when the consumer uses a different Kotlin version.
pluginManagement {
repositories {
google()
maven("https://maven.rohittp.com")
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
maven("https://maven.rohittp.com")
mavenCentral()
}
}
Apply the plugin to every Kotlin module that declares debug inputs, and to the module that hosts DebugInputsPage() so its call site can aggregate those inputs:
plugins {
id("com.rohittp.debug-input") version "latest"
}
Add the optional Compose UI artifact in that page-hosting module:
dependencies {
implementation("com.rohittp:debug-input-compose:latest")
}
The Gradle plugin adds debug-input-runtime to instrumented source sets automatically. The Compose artifact exposes it transitively to the UI host.
02 · Quick start
Annotate, host, tune
The declaration keeps its normal Kotlin type and default.
Add one composable to your existing debug settings screen.
Reads resolve the persisted override until it is reset.
import com.rohittp.debuginput.DebugInput
/** Multiplier applied to preview playback. */
@DebugInput
val playbackSpeed: Double = 1.0
import com.rohittp.debuginput.compose.DebugInputsPage
@Composable
fun DebugSettings() {
DebugInputsPage()
}
The compiler rewrites the call site so DebugInputsPage() receives every
generated descriptor reachable from that module. There is no list to maintain.
03 · Ordinary values
Keep tuning values where they belong
Annotate top-level or object properties. The property's KDoc becomes its help text, and its declaring file, class, or object becomes the default section.
/**
* Keeps the camera inside the route bounds
* during fast preview scrubbing.
*/
@DebugInput
val cameraPadding = 48.0
/** Draw route labels in preview. */
@DebugInput
val showLabels = true
Physics
48.0trueExplicit metadata is optional
Use docs only when the UI needs different prose from source readers. Use
section to merge related values from different files into one page.
@DebugInput(
section = "Animation tuning",
docs = "Tester-facing explanation shown in the app.",
)
val trailOpacity = 0.72
04 · Enum inputs
Turn a constants table into one tuning page
Annotating an enum class instruments every supported constructor val on
every constant. This works well for large, documented tuning tables.
/** Values controlling route animation and rendering. */
@DebugInput(section = "Animation constants")
enum class MagicNumbers(vararg val values: Double) {
/** Widths for low, normal, and high emphasis. */
ROUTE_LINE_WIDTHS(2.0, 4.0, 6.0),
/** Scale ladder used by the flag marker. */
FLAG_SCALES(0.6, 0.8, 1.0),
}
Rows are named ROUTE_LINE_WIDTHS; the redundant property suffix is omitted.
Rows are named FREE.limit, FREE.label, and so on.
Enum getters use an ordinal-indexed id table. No string concatenation happens per read.
Constants with anonymous bodies still inherit the ordinal used by the generated table.
Override one entry's documentation
@DebugInput(docs = "Shorter tester-facing explanation.")
MINIMUM_DURATION_CEILING_SEC(8.0)
The class-level annotation creates the inputs. An annotation on one entry only overrides that entry's documentation.
05 · Sections and documentation
Source structure becomes navigation
Every compiler-generated section is a root card leading to a dedicated subpage. The
default title is the declaring file, class, object, or enum name. An explicit
section title groups ordinary values with the same title inside a module.
| Declaration | Default section | Description |
|---|---|---|
| Top-level property | Source file name | Property KDoc |
| Class or object property | Class or object name | Property KDoc |
| Annotated enum class | Enum name | Enum class KDoc |
Explicit section | Provided title | Declaration KDoc |
KDoc precedence
- 1Explicit
docsUse when the in-app explanation intentionally differs from source documentation.
- 2Declaration KDoc
The normal path for values, enum classes, and enum entries.
- 3Empty
The row remains usable without an information action.
Block tags such as @param and @see are dropped, qualified KDoc links are shortened to their final name, star margins are collapsed, and paragraph breaks are retained. The full body is kept.
06 · Debug UI
A settings page that scales from one value to hundreds
DebugInputsPage() inherits the host app's Material theme and fills its
bounded parent. Sections become subpages, keeping the root readable, and a search bar
on every page finds any input by name.
Validated number and text fields, Boolean controls, enum selection, and element editors for composites.
Changed badges, per-row reset, changed counts on sections, and one global reset.
Property or enum-entry KDoc is available beside the value; enum KDoc introduces its section page.
Inputs from dependent modules are collected, de-duplicated by id, and sorted deterministically.
A search bar on every page matches names and ids. From the root it reaches into section pages and opens the page at the matching input; inside a section it filters that page.
Search
Every page has a search bar at the top. It matches an input's display name or its stable
id, ignoring case, so both speed and app.physics find
com.app.physics.speed. What the results look like depends on where you search:
| Searching from | Covers | A match shows as |
|---|---|---|
| The root page | Every input, including those on section pages | An editable row when the input sits on the root; otherwise a result naming its section. Tapping the result opens that section page, scrolled to the input and outlined. |
| A section page | That page's inputs only | The input's editable row; the rest of the page is filtered out. |
Back from a section page returns to the root with the same query and results. Clearing the
field restores the full page. Search needs no setup and no API: it is part of
DebugInputsPage() from 0.2.0.
The composable does not install a MaterialTheme or its own navigation destination. Put it inside your existing debug-only settings flow and give it bounded height.
07 · Tester handoff
Copy changed defaults as JSON
The page exports only effective overrides that differ from the declaration's current default. The payload is stable-id sorted, versioned, and copied directly to the clipboard so testers can send it to developers.
{
"version": 1,
"changes": [
{
"id": "com.app.Animation.playbackSpeed",
"module": ":shared",
"section": "Animation",
"name": "playbackSpeed",
"type": "kotlin.Double",
"default": 1.0,
"value": 1.35
}
]
}
Dormant overrides from renamed, removed, or type-changed inputs are excluded. Arrays and composites are exported as JSON arrays; enum values and characters are strings.
08 · Supported values
Built-in editors for common Kotlin shapes
Scalars
ByteShortIntLongFloatDoubleBooleanCharStringChoice
EnumCollections
List<T>Set<T>Array<T>Primitive arrays
ByteArrayShortArrayIntArrayLongArrayFloatArrayDoubleArrayBooleanArrayCharArrayTuples
Pair<A, B>Triple<A, B, C>Collection elements and tuple slots must use supported scalar shapes. Nested containers, maps, and enums inside containers are rejected by design.
09 · Runtime model
Generated at compile time, cheap at read time
@DebugInput valRead path
A generated getter calls a type-specific resolver. Scalar reads use a debug-build check, volatile map read, and lookup. Enum-class inputs load their stable id from an ordinal table.
Persistence
Android uses dependency-free automatic initialization plus SharedPreferences. iOS uses an isolated NSUserDefaults suite. Hydration is lazy on first read.
Cross-module pages
Each instrumented module publishes a descriptor manifest. The consuming page call aggregates direct dependency descriptors hierarchically.
Stable identity
Property ids derive from package and declaration identity. Section display titles are separate from page identity, so equal file names do not collide.
10 · Production behavior
Defaults remain authoritative in release builds
On Android application release compilations, the transform is disabled. For Kotlin Multiplatform Android libraries, AGP exposes one Android variant, so generated calls can remain in the artifact; the runtime debug check still guarantees defaults. On iOS, debug/release identity is resolved at final link time.
The export is a developer handoff. Review it, update the Kotlin declarations, and let those new source defaults ship through the normal production process.
11 · Current boundaries
Know what is deliberately outside the line
- ×Exact Kotlin pin
The compiler plugin currently supports Kotlin
2.4.20, not a range. - ×Ordinary instance properties
An annotated property on an ordinary class instance has no statically reachable descriptor default. Prefer top-level, object, or enum-class inputs.
- ×No custom renderers yet
Unsupported type shapes render no editor. Maps and nested containers are rejected.
- ×No iOS x64 Compose page
The runtime supports iOS x64, but the Compose dependency line no longer publishes that simulator target.
- ×Debug tuning is not synchronized
Overrides are local to one app installation. Use Copy JSON for collaboration.
12 · Troubleshooting
Common integration failures
Plugin com.rohittp.debug-input was not found
Add https://maven.rohittp.com inside pluginManagement.repositories in the root settings file. A dependency repository alone cannot resolve a Gradle plugin marker.
debug-input-runtime or Compose cannot be resolved
Add the same repository inside dependencyResolutionManagement.repositories. Plugin and library resolution are separate.
The build reports a Kotlin version mismatch
Use Kotlin 2.4.20 for DebugInput latest. This guard prevents binary-incompatible compiler internals from failing later with obscure FIR or IR errors.
Which DebugInput version matches my Kotlin version?
Each release line is built for exactly one Kotlin minor: 0.2.x for Kotlin 2.4.x (built on 2.4.20) and 0.1.x for Kotlin 2.3.x (built on 2.3.21). A project still on Kotlin 2.3 should stay on 0.1.0; upgrading to 0.2.0 needs no source changes beyond the Kotlin bump.
The page is empty
Confirm the plugin is applied to the module that declares inputs and that DebugInputsPage() is compiled in an instrumented module. An empty page is expected in release builds.
A value resets after editing
Invalid text is not stored. For composites, every element must match the declared shape. A stored override from an older type is dormant and the current declaration resolves its default.
Two sections have the same title
Default sections keep separate internal page identities even when two files share a name. If they are conceptually one page, assign the same explicit section title.
13 · Public API
Small surface, generated behavior
@DebugInput(docs = "", section = "")Marks a property or enum class. Both parameters are optional and fall back to KDoc and source structure.
DebugInputsPage(descriptors = emptyList(), modifier = Modifier)Renders the generated debug input catalogue. Normal source calls omit descriptors; the compiler supplies them.
Registry and descriptor internals are intentionally marked internal opt-in API. Build integrations should depend on the annotation, Gradle plugin, and page rather than on generated function names or codec details.