Guidance for AI agents working in this repository. Read alongside CLAUDE.md
(build/test commands, architecture, style rules). This file lists Kotlin/Android
antipatterns to avoid when generating or modifying code.
- Do not use
GlobalScope. Use an injectedCoroutineScopetied to a lifecycle (viewModelScope,lifecycleScope) or a scope owned by the class. For fire-and-forget work that must outlive the lifecycle owner, inject@Named(APPLICATION_SCOPE) CoroutineScope— never reach forGlobalScope. - Do not use
runBlockingon the main thread or in production code. It is acceptable only in tests. - Do not hardcode
Dispatchers.IO/Dispatchers.Maininside classes. InjectCoroutineDispatchervia constructor with@Named(IO_THREAD)so it can be swapped in tests. SeeReaderPostRepository.ktfor the canonical pattern. - Do not launch coroutines from constructors or
initblocks. Start them from a lifecycle-aware entry point. - Do not use
Thread.sleepto wait for async work. Use coroutines,delay, or proper synchronization. Never in tests either — userunTestand virtual time. - Do not use
AsyncTask,Handler(Looper.getMainLooper())for new code, or rawThread. Prefer coroutines.
- Avoid
!!(non-null assertion). If a value is guaranteed non-null, restructure to express that in the type. If not, handle the null branch. Exception: the Fragment view-binding backing-field idiom (private val binding get() = _binding!!) is accepted in this codebase. - Avoid
lateinit varfor values that have a sensible default or can be constructor-injected. Reserve it for DI-injected fields. For view bindings, use the nullable backing-field idiom described below in Android Lifecycle & Memory Leaks. - Do not use
!!onintent.extras,arguments, orsavedInstanceState. Use safe calls with sensible fallbacks. - Do not write
if (x != null) x.foo(). Usex?.foo()orx?.let { ... }. - Prefer
valovervar. Only usevarwhen reassignment is required. - Do not use
Anyas a type when a sealed class / interface fits.
- Do not store
Activity,Fragment,View, orContextreferences incompanion object, singletons, orobjectdeclarations. UseapplicationContextif a Context is truly required at app scope. - Do not capture
Activity/Fragmentin long-lived callbacks, listeners, or coroutine scopes without lifecycle awareness. - Do not access
bindingafteronDestroyViewin Fragments. Use the nullable backing-field pattern already used across the codebase (private var _binding: FooBinding? = nullexposed viaprivate val binding get() = _binding!!, nulled inonDestroyView). SeeReaderAuthorProfileBottomSheetFragment.ktfor an example. For ViewHolders, use theViewGroup.viewBinding(...)extension inutil/extensions/ViewGroupExtensions.kt. - Do not start work in
onCreatethat must outlive the screen. UseViewModel,WorkManager, or a foreground service as appropriate. - Do not register
BroadcastReceiver/observers without unregistering in the matching lifecycle callback.
- Do not catch broad exceptions (
Exception,Throwable) without logging viaAppLogor rethrowing. Prefer catching the specific exception when practical. Broad catches are acceptable at network/IO boundaries as long as the error flows through the existingAppLog/Sentry pipeline. If you intentionally swallow an exception, add a one-line comment explaining why. - Do not throw raw
RuntimeException/IllegalStateExceptionfor control flow or expected error paths. Use sealed result types or nullable returns.check/require/errorfor genuine precondition violations is fine. - Do not log exceptions with
printStackTrace(). UseAppLogso output ends up in the right place in release builds.
- Do not use
findViewById. Use View Binding (existing XML) or Compose (new screens). Already inCLAUDE.md; restated because it is the most common regression. - Do not use deprecated APIs (
startActivityForResult,onActivityResult,getDrawable(int),getColor(int)without theme,Handler()no-arg ctor, etc.). Use Activity Result APIs,ContextCompat,ResourcesCompat, etc. - Do not use reflection. Already in
CLAUDE.md; restated for emphasis. - Do not use
SimpleDateFormatas a static/companion field. It is not thread-safe. Construct per use, or usejava.time/DateTimeFormatter. - Do not hardcode user-facing strings. Add them to
strings.xmland reference viaR.string.*orUiString. - Do not hardcode dimensions, colors, or styles in code. Use resources and the design system.
- Do not perform I/O, network, or DB work on the main thread.
- Do not instantiate dependencies with
new/constructor calls inside classes that should receive them via DI. - Do not use service locators or
objectsingletons for stateful dependencies. Use@Injectconstructor injection. - Do not inject
Contextdirectly when@ApplicationContextis what you need. Annotate explicitly to avoid leaking an Activity context.
- Inside a composable, always wrap
mutableStateOfinremember, and do not mutate state during composition. Do mutations in event handlers orLaunchedEffect. Top-levelmutableStateOfproperties on a class (e.g., aViewModel) are fine. - Do not create
ViewModelinstances inside composables via constructors. UsehiltViewModel()/viewModel(). - Do not pass
ViewModeldeep into the composable tree. Hoist state and pass plain data + lambdas. - Do not launch coroutines from composables without
LaunchedEffect/rememberCoroutineScope. - Do not perform expensive work in the composable body. Wrap in
remember/derivedStateOf.
- Do not write tests that depend on real time, network, or device state.
Use fakes,
runTest, virtual time, and injected dispatchers. - Do not assert on
toString()or implementation details. Assert on observable behavior. - Do not use
Thread.sleepin tests. Use coroutine test utilities. - Do not mock data classes or types you own when a real instance / fake works. Reserve mocks for collaborators.
- Do not introduce TODOs without a tracking issue or owner. Already covered
by "no FIXME" in
CLAUDE.md; same spirit applies to TODOs. - Do not leave commented-out code. Delete it; git history preserves it.
- Do not add wrapper functions, interfaces, or abstractions "for future flexibility" when there is exactly one caller and one implementation.
- Do not duplicate strings across
strings.xmltranslations. Only edit the basevalues/strings.xml; translations are managed externally. Exception: when removing a string, delete it from everyvalues-*/strings.xmltoo — lint'sExtraTranslationrule will fail otherwise. - Do not add new dependencies in
build.gradledirectly. Add togradle/libs.versions.toml(already inCLAUDE.md).
- Do not log credentials, tokens, cookies, personal data, or full URLs that
may contain auth. Even at
DEBUGlevel. - Do not store secrets in source. Use
secrets.properties/ Gradle properties. - Do not disable SSL verification, hostname verification, or accept all certificates. Even in debug builds.
- Do not use
MODE_WORLD_READABLE/MODE_WORLD_WRITEABLEfor SharedPreferences or files.