Skip to content

Commit c45547a

Browse files
adalpariclaude
andauthored
Add AGENTS.md with Kotlin/Android antipatterns guide (#22919)
* Add AGENTS.md with Kotlin/Android antipatterns guide Provides AI agents a quick-reference list of antipatterns to avoid, complementing CLAUDE.md's existing build/style guidance. * Refine AGENTS.md antipatterns based on review feedback Tighten four rules that were either inaccurate or under-specified: Compose state, IllegalStateException usage, Fragment view-binding guidance with concrete examples, and strings.xml removal caveat. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix AGENTS.md review nits: example, !! carve-out, Compose wording Replace inaccurate Fragment binding example, add explicit exception for the view-binding `!!` idiom, and soften the Compose body rule to refer to expensive work rather than allocations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Refine AGENTS.md error-handling and dispatcher guidance Soften broad-catch rule to allow logged catches at network/IO boundaries, and point dispatcher injection to ReaderPostRepository.kt as the canonical example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Refine AGENTS.md review nits: APPLICATION_SCOPE, lateinit, catch, Compose - Concurrency: point at @nAmed(APPLICATION_SCOPE) for outlive-the-screen work to close the GlobalScope loophole. - Nullability: drop "view bindings" from lateinit carve-out; defer to the nullable backing-field idiom in the Lifecycle section. - Error handling: merge overlapping broad-catch and catch-and-ignore bullets into one rule. - Compose: clarify rule is "no state mutation during composition" — event handlers and LaunchedEffect are fine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Rephrase AGENTS.md Compose mutableStateOf rule Frame the rule positively (wrap in `remember` inside composables) and carve out top-level `mutableStateOf` properties on a class so agents don't flag legitimate ViewModel usage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b70003f commit c45547a

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# AGENTS.md
2+
3+
Guidance for AI agents working in this repository. Read alongside `CLAUDE.md`
4+
(build/test commands, architecture, style rules). This file lists Kotlin/Android
5+
**antipatterns to avoid** when generating or modifying code.
6+
7+
## Concurrency & Coroutines
8+
9+
- **Do not use `GlobalScope`.** Use an injected `CoroutineScope` tied to a
10+
lifecycle (`viewModelScope`, `lifecycleScope`) or a scope owned by the class.
11+
For fire-and-forget work that must outlive the lifecycle owner, inject
12+
`@Named(APPLICATION_SCOPE) CoroutineScope` — never reach for `GlobalScope`.
13+
- **Do not use `runBlocking` on the main thread or in production code.** It is
14+
acceptable only in tests.
15+
- **Do not hardcode `Dispatchers.IO`/`Dispatchers.Main` inside classes.** Inject
16+
`CoroutineDispatcher` via constructor with `@Named(IO_THREAD)` so it can be
17+
swapped in tests. See `ReaderPostRepository.kt` for the canonical pattern.
18+
- **Do not launch coroutines from constructors or `init` blocks.** Start them
19+
from a lifecycle-aware entry point.
20+
- **Do not use `Thread.sleep`** to wait for async work. Use coroutines, `delay`,
21+
or proper synchronization. Never in tests either — use `runTest` and virtual
22+
time.
23+
- **Do not use `AsyncTask`, `Handler(Looper.getMainLooper())` for new code, or
24+
raw `Thread`.** Prefer coroutines.
25+
26+
## Nullability & Kotlin Idioms
27+
28+
- **Avoid `!!` (non-null assertion).** If a value is guaranteed non-null,
29+
restructure to express that in the type. If not, handle the null branch.
30+
Exception: the Fragment view-binding backing-field idiom
31+
(`private val binding get() = _binding!!`) is accepted in this codebase.
32+
- **Avoid `lateinit var` for values that have a sensible default or can be
33+
constructor-injected.** Reserve it for DI-injected fields. For view
34+
bindings, use the nullable backing-field idiom described below in
35+
*Android Lifecycle & Memory Leaks*.
36+
- **Do not use `!!` on `intent.extras`, `arguments`, or `savedInstanceState`.**
37+
Use safe calls with sensible fallbacks.
38+
- **Do not write `if (x != null) x.foo()`.** Use `x?.foo()` or `x?.let { ... }`.
39+
- **Prefer `val` over `var`.** Only use `var` when reassignment is required.
40+
- **Do not use `Any` as a type when a sealed class / interface fits.**
41+
42+
## Android Lifecycle & Memory Leaks
43+
44+
- **Do not store `Activity`, `Fragment`, `View`, or `Context` references in
45+
`companion object`, singletons, or `object` declarations.** Use
46+
`applicationContext` if a Context is truly required at app scope.
47+
- **Do not capture `Activity`/`Fragment` in long-lived callbacks, listeners, or
48+
coroutine scopes** without lifecycle awareness.
49+
- **Do not access `binding` after `onDestroyView` in Fragments.** Use the
50+
nullable backing-field pattern already used across the codebase
51+
(`private var _binding: FooBinding? = null` exposed via
52+
`private val binding get() = _binding!!`, nulled in `onDestroyView`). See
53+
`ReaderAuthorProfileBottomSheetFragment.kt` for an example. For
54+
ViewHolders, use the
55+
`ViewGroup.viewBinding(...)` extension in
56+
`util/extensions/ViewGroupExtensions.kt`.
57+
- **Do not start work in `onCreate` that must outlive the screen.** Use
58+
`ViewModel`, `WorkManager`, or a foreground service as appropriate.
59+
- **Do not register `BroadcastReceiver`/observers without unregistering** in
60+
the matching lifecycle callback.
61+
62+
## Error Handling
63+
64+
- **Do not catch broad exceptions (`Exception`, `Throwable`) without logging
65+
via `AppLog` or rethrowing.** Prefer catching the specific exception when
66+
practical. Broad catches are acceptable at network/IO boundaries as long
67+
as the error flows through the existing `AppLog`/Sentry pipeline. If you
68+
intentionally swallow an exception, add a one-line comment explaining
69+
why.
70+
- **Do not throw raw `RuntimeException`/`IllegalStateException` for control
71+
flow or expected error paths.** Use sealed result types or nullable
72+
returns. `check`/`require`/`error` for genuine precondition violations is
73+
fine.
74+
- **Do not log exceptions with `printStackTrace()`.** Use `AppLog` so output
75+
ends up in the right place in release builds.
76+
77+
## Android APIs
78+
79+
- **Do not use `findViewById`.** Use View Binding (existing XML) or Compose
80+
(new screens). Already in `CLAUDE.md`; restated because it is the most
81+
common regression.
82+
- **Do not use deprecated APIs** (`startActivityForResult`, `onActivityResult`,
83+
`getDrawable(int)`, `getColor(int)` without theme, `Handler()` no-arg ctor,
84+
etc.). Use Activity Result APIs, `ContextCompat`, `ResourcesCompat`, etc.
85+
- **Do not use reflection.** Already in `CLAUDE.md`; restated for emphasis.
86+
- **Do not use `SimpleDateFormat` as a static/companion field.** It is not
87+
thread-safe. Construct per use, or use `java.time` / `DateTimeFormatter`.
88+
- **Do not hardcode user-facing strings.** Add them to `strings.xml` and
89+
reference via `R.string.*` or `UiString`.
90+
- **Do not hardcode dimensions, colors, or styles in code.** Use resources
91+
and the design system.
92+
- **Do not perform I/O, network, or DB work on the main thread.**
93+
94+
## Dependency Injection (Hilt/Dagger)
95+
96+
- **Do not instantiate dependencies with `new`/constructor calls inside
97+
classes that should receive them via DI.**
98+
- **Do not use service locators or `object` singletons for stateful
99+
dependencies.** Use `@Inject` constructor injection.
100+
- **Do not inject `Context` directly when `@ApplicationContext` is what you
101+
need.** Annotate explicitly to avoid leaking an Activity context.
102+
103+
## Compose
104+
105+
- **Inside a composable, always wrap `mutableStateOf` in `remember`, and do
106+
not mutate state during composition.** Do mutations in event handlers or
107+
`LaunchedEffect`. Top-level `mutableStateOf` properties on a class
108+
(e.g., a `ViewModel`) are fine.
109+
- **Do not create `ViewModel` instances inside composables via constructors.**
110+
Use `hiltViewModel()` / `viewModel()`.
111+
- **Do not pass `ViewModel` deep into the composable tree.** Hoist state and
112+
pass plain data + lambdas.
113+
- **Do not launch coroutines from composables without `LaunchedEffect` /
114+
`rememberCoroutineScope`.**
115+
- **Do not perform expensive work in the composable body.** Wrap in
116+
`remember` / `derivedStateOf`.
117+
118+
## Testing
119+
120+
- **Do not write tests that depend on real time, network, or device state.**
121+
Use fakes, `runTest`, virtual time, and injected dispatchers.
122+
- **Do not assert on `toString()` or implementation details.** Assert on
123+
observable behavior.
124+
- **Do not use `Thread.sleep` in tests.** Use coroutine test utilities.
125+
- **Do not mock data classes or types you own when a real instance / fake
126+
works.** Reserve mocks for collaborators.
127+
128+
## Code Hygiene
129+
130+
- **Do not introduce TODOs without a tracking issue or owner.** Already covered
131+
by "no FIXME" in `CLAUDE.md`; same spirit applies to TODOs.
132+
- **Do not leave commented-out code.** Delete it; git history preserves it.
133+
- **Do not add wrapper functions, interfaces, or abstractions "for future
134+
flexibility"** when there is exactly one caller and one implementation.
135+
- **Do not duplicate strings across `strings.xml` translations.** Only edit
136+
the base `values/strings.xml`; translations are managed externally.
137+
Exception: when *removing* a string, delete it from every
138+
`values-*/strings.xml` too — lint's `ExtraTranslation` rule will fail
139+
otherwise.
140+
- **Do not add new dependencies in `build.gradle` directly.** Add to
141+
`gradle/libs.versions.toml` (already in `CLAUDE.md`).
142+
143+
## Security & Privacy
144+
145+
- **Do not log credentials, tokens, cookies, personal data, or full URLs that
146+
may contain auth.** Even at `DEBUG` level.
147+
- **Do not store secrets in source.** Use `secrets.properties` / Gradle
148+
properties.
149+
- **Do not disable SSL verification, hostname verification, or accept all
150+
certificates.** Even in debug builds.
151+
- **Do not use `MODE_WORLD_READABLE`/`MODE_WORLD_WRITEABLE`** for
152+
SharedPreferences or files.

0 commit comments

Comments
 (0)