| name | ha-android-kotlin-style |
| description | Home Assistant Android Kotlin style. Use when declaring constants, modeling data or logic types, working with time or dates, choosing visibility, naming, or writing KDoc and comments. |
HA Android Kotlin Style
Use this skill when writing or reviewing Kotlin code style: constants, types, immutability, time, visibility, and documentation.
KTLint enforces formatting via .editorconfig; format with ./gradlew :build-logic:convention:ktlintFormat ktlintFormat. Style rationale lives at https://developers.home-assistant.io/docs/android/codestyle and https://developers.home-assistant.io/docs/android/best_practices.
Constants
Replace magic numbers and strings with named constants, placed near their usage:
private const val MAX_RETRIES = 3
object NetworkConstants {
const val MAX_RETRIES = 3
}
Avoid companion object for constants unless they must be accessed externally under the class name or several classes in one file need the same constant name. Use @VisibleForTesting when a constant is only exposed for tests.
Strong Types, Never Strings for Logic
- Use sealed classes/interfaces over enums or strings for logic control. Sealed subtypes can carry their own data, and
when without an else branch makes the compiler catch missing cases.
- Reserve strings for raw third-party values or UI display; wrap unavoidable strings in a strong type such as an inline value class. Avoid string manipulation to build structured values (URLs, identifiers) — work with the typed object (
Url, a value class) and convert at the boundary.
- Never use
Pair/Triple in an API — a caller can't tell what the components mean. Define a small data class with named fields instead.
- Use Kotlin
Duration/Instant instead of primitive types for time. If a primitive is unavoidable, put the unit in the name (THRESHOLD_MS), and convert to a strong type as early as possible. Note that Duration is not a compile-time constant: val STOP_TIMEOUT = 500.milliseconds, not const val.
- Locale-sensitive operations like
lowercase() must not feed logic: for sort keys, identifiers, and search matching use lowercase(Locale.ROOT), otherwise matching breaks under locales like Turkish (dotted/dotless I).
- Never use
System.currentTimeMillis; use kotlin.time.Clock injected through Hilt, and expose the clock as a parameter or attribute so tests can control it.
Immutability and Functions
- Store data in immutable classes exposing
copy() or functions returning a new modified instance.
- Keep functions small (under 50 lines), single-responsibility, with meaningful names. If a function is hard to name, it does too much.
- Prefer composition over inheritance.
Keep It Direct
- Don't introduce an abstraction (interface, seam, wrapper) for a single implementation or a hypothetical future need — use the proven library or API directly. Abstract only when a second real use appears, or to hide an implementation from outside its module (public interface,
internal implementation bound through DI) — see ha-android-architecture for the interface + *Impl criteria.
- Don't store what can be derived: a field duplicating a map key, an ID rebuildable from its parts, or a flag mirroring other state is a bug waiting for a desync.
- When calling a function with several parameters of the same or primitive types, use named parameters instead of relying on order.
Visibility
Default to the narrowest visibility: private to the file or class. Before making anything public, check whether internal suffices.
Imports
When adding an import, add the import and its usage in the same edit — linters and hooks strip unused imports, so an import added in a separate step gets removed. The reverse also bites: after removing or moving code, delete the imports that became unused — ktlint fails CI on them.
Documentation and Comments
- Every public function needs KDoc focused on the functionality, not the implementation. Mention implementation details only when they affect usage (synchronization constraints, what it throws).
- Keep it to the essential and straight to the point: state the contract in as few words as it takes and stop. Don't narrate the implementation, restate the obvious from the signature, explain rationale that belongs in a commit or issue, or pad with AI-sounding prose — a one-line function usually needs a one-line doc.
- Keep KDoc in sync with the code: when changing what a function does, targets, or emits, update its documentation in the same change — stale KDoc naming the wrong class or error type misleads the next reader.
- Explain the "why", not just the "what". Write for developers who may not be native English speakers: no abbreviations, add references when useful.
- Avoid TODOs; if one is required, link an existing GitHub issue:
// TODO Missing feature https://github.com/home-assistant/android/issues/404. Don't invent an issue number or open a new issue yourself — ask the user for the issue, or leave the TODO out.
- GitHub references in comments and KDoc are always full HTTP links, never bare
#1123 — a number is ambiguous and unclickable outside GitHub. When the reference points at code (a file, line, or commit), use a permalink (commit-SHA URL, y on GitHub) so the link survives file moves and edits.