This file provides guidance to Claude Code when working with code in this repository.
./gradlew lintCheck- Run ktlint and detekt checks (same as CI)./gradlew ktlintFormat- Automatically fix code style issues./gradlew test- Run unit tests./gradlew clean- Remove all build artifacts
Single-module project with the main app under :app. Build logic lives in buildSrc/ and convention-plugins/.
MVVM with Arkitekt library:
- ViewModel (
*ViewModel.kt) - extendsBaseViewModel<ViewState>, implementsActionsinterface from the screen - ViewState (
*ViewState.kt) - holds mutable Compose state (var counter by mutableIntStateOf(0)) - Events (
*Events.kt) - sealed class of one-time events (navigation, toasts); collected viaEventsEffect - Screen (
*Screen.kt) - Composable; receivesviewState, collects events, delegates interactions toActions
Actions are defined as a nested interface inside the Screen object:
object HomeScreen {
interface Actions {
fun onIncrementCounter()
fun onNavigateToDetail()
}
}abstract val viewState: VS— injected ViewStatesendEvent(event: Event<VS>)— sends a one-time event to the UI layer
- Provides
coroutineScopebacked byviewModelScope - Inherits all
CoroutineScopeOwnerextension functions below
Always extend the Arkitekt base classes — never use plain suspend functions with invoke():
// UseCase<ARGS, RESULT> — single async operation
class SignInUseCase @Inject constructor(...) : UseCase<Unit, Unit>() {
override suspend fun build(args: Unit) { /* business logic */ }
}
// FlowUseCase<ARGS, T> — streaming operation
class ObserveSomethingUseCase @Inject constructor(...) : FlowUseCase<Unit, MyModel>() {
override fun build(args: Unit): Flow<MyModel> = /* … */
}// Async execution with callbacks (preferred; cancels previous by default)
someUseCase.execute {
onStart { /* show loading */ }
onSuccess { value -> sendEvent(MyEvent) } // sendEvent is non-suspend, safe here
onError { throwable -> /* … */ }
}
// Suspend execution — use inside launchWithHandler for error handling
launchWithHandler {
val result = someUseCase.execute() // returns Result<T>
result.getOrNull() // or getOrThrow(), getOrDefault(), fold(…)
}
// Flow use case
someFlowUseCase.execute {
onStart { }
onNext { value -> }
onError { throwable -> }
onComplete { }
}EventsEffect {
onEvent<MyEvent> { /* handle */ }
}Hilt throughout:
@HiltAndroidApponApp,@AndroidEntryPointonAppActivity@HiltViewModelon ViewModels,@ViewModelScopedon ViewState- Modules:
ApplicationModule(singletons),NetworkModule(Retrofit/OkHttp)
Flavor dimension: api with three flavors:
- mock - local mock data
- dev - development API
- prod - production API
Build types: debug, enterprise (minified, debug key), release (minified, release key).
- Max line length: 140 characters
- Indent: 4 spaces, trailing commas allowed
- Ktlint code style:
android_studio - Detekt config:
config/detekt.yml
HomeViewModel,HomeViewState,HomeEvents,HomeScreen- Event objects:
NavigateToDetailEvent,NavigateBackEvent - Action methods:
onIncrementCounter(),onNavigateToDetail()(prefixon) - Composable functions: PascalCase; preview functions:
private fun HomePreview()
Material3 via MaterialTheme. Colors, typography, shapes, and dimensions defined in app/src/main/kotlin/.../ui/theme/.
Use Dimensions.kt tokens for spacing — avoid raw dp literals where theme tokens exist.
- Unit tests: JUnit 4 + MockK
- Instrumented tests: AndroidJUnit4
- Run unit tests:
./gradlew test - Run module tests:
./gradlew :app:test