This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Hedvig Android app - A modern Android application built with Jetpack Compose, Apollo GraphQL, and Kotlin. The app uses a highly modular architecture with 80+ modules organized into feature, data, and core layers.
# 1. Download GraphQL schema (required before building)
./gradlew downloadOctopusApolloSchemaFromIntrospection
# 2. Download translations from Lokalise (required before building)
./gradlew downloadStrings
# 3. Build and sync the project
./gradlew buildPrerequisites:
lokalise.propertiesfile with credentials (from 1Password)~/.gradle/gradle.propertieswith GitHub Packages token (PAT withread:packagespermission)- See
scripts/ci-prebuild.shfor reference
# Build the app
./gradlew :app:assemble
# Run all tests
./gradlew test
# Run tests for a specific module
./gradlew :feature-home:test
# Run unit tests
./gradlew testDebugUnitTest
# Formatting
./gradlew ktlintCheck # Check formatting
./gradlew ktlintFormat # Auto-format files
# Linting
./gradlew lint
# Clean build
./gradlew clean
# Generate module dependency graph (requires graphviz: brew install graphviz)
./gradlew :generateProjectDependencyGraph
# Find unused resources
./gradlew :app:lint -Prur.lint.onlyUnusedResources
./gradlew :app:removeUnusedResourcesDebugThe codebase is organized under /app with 80+ modules following a strict modularization pattern:
- app/ - Main application module
- feature/ - Feature modules (feature-home, feature-chat, feature-login, etc.)
- data/ - Data layer modules (data-contract, data-chat, data-addons, etc.)
- core/ - Core utilities (core-common, core-datastore, core-resources, etc.)
- apollo/ - GraphQL client modules (apollo-octopus-public, apollo-core, etc.)
- *navigation/ - Navigation modules (navigation-compose, navigation-core, etc.)
- *design-system/ - Design system components
- ui/ - Shared UI components
- auth/ - Authentication modules
- database/ - Room database modules
- language/ - Localization modules
- Other utilities - payment, tracking, logging, featureflags, etc.
Critical architectural rule: Feature modules CANNOT depend on other feature modules. This is enforced at build time by the hedvig.gradle.plugin.
{name}-public- Public APIs and interfaces (often KMP-compatible){name}-android- Android-specific implementations{name}-test- Test utilities- No suffix for main implementation modules
However, if a module is KMP compatible, there is no need for the -public or -android suffix.
The android-specific code lives inside the androidMain directory instead. (see :language-core)
- Release (
com.hedvig.app) - Production builds for Play Store - Staging (
com.hedvig.app) - Internal testing via Firebase App Distribution (staging backend) - Develop (
com.hedvig.dev.app) - Development builds (staging backend)
The app uses Molecule (Cash App's library) for reactive state management:
// ViewModels delegate to Presenters
class FeatureViewModel(
useCaseProvider: Provider<UseCase>,
) : MoleculeViewModel<FeatureEvent, FeatureUiState>(
FeatureUiState.Loading,
FeaturePresenter(/* ... */),
)
// Presenters contain presentation logic
class FeaturePresenter : MoleculePresenter<FeatureEvent, FeatureUiState> {
@Composable
override fun present(events: Flow<FeatureEvent>): FeatureUiState {
// Composable state management logic
}
}Flow: User Action → Event → Presenter → UiState → UI
Each feature module follows this structure:
feature-{name}/
├── build.gradle.kts
└── src/main/kotlin/com/hedvig/android/feature/{name}/
├── ui/
│ ├── {Name}Destination.kt # Composable entry point
│ ├── {Name}ViewModel.kt # MoleculeViewModel
│ ├── {Name}Presenter.kt # MoleculePresenter
│ └── {Name}Layout.kt # UI components
├── navigation/
│ └── {Name}Graph.kt # Navigation setup
└── di/
└── {Name}Module.kt # Koin DI module
Uses type-safe Navigation Compose with custom extensions:
// Destinations are serializable sealed interfaces
sealed interface FeatureDestination : Destination {
@Serializable
data object Graph : FeatureDestination
@Serializable
data class Detail(val id: String) : FeatureDestination
}
// Navigation graphs
fun NavGraphBuilder.featureGraph(
navigator: Navigator,
) {
navgraph<FeatureDestination.Graph> {
navdestination<FeatureDestination.Detail> { backStackEntry ->
// Composable UI
}
}
}Top-level navigation graphs: Home, Insurances, Forever, Payments, Profile
Uses Koin with modular configuration:
// Each module has its own DI module
val featureModule = module {
viewModel { FeatureViewModel(get()) }
single { FeatureUseCase(get(), get()) }
}
// All modules are included in ApplicationModule
val applicationModule = module {
includes(
featureModule,
dataModule,
networkModule,
// ... 40+ modules
)
}Patterns:
- Use
Provider<T>when we need a different implementation for the demo mode of the App, which we very rarely do. We always do that usingProdOrDemoProvider - Each feature/data module has its own DI module
- Common dependencies (logging, tracking) auto-injected by build plugin
Data modules follow this structure when they are not KMP compatible:
data-{domain}/
├── data-{domain}-public/ # Interfaces/models
│ └── src/main/
└── data-{domain}-android/ # Android implementation (optional)
And they follow this structure when they are KMP compatible:
data-{domain}/
└── data-{domain}/ # Interfaces/models (KMP)
└── src/commonMain/
Patterns:
- Repository pattern with interfaces
- Apollo GraphQL queries/mutations
- Use cases for business logic
- Room database for local persistence
- Jetpack Compose - 100% Compose, no XML layouts
- Material 3 - Window size classes, theming. Only used internally by our design-system-internals
- Coil - Image loading (SVG, GIF, PDF support)
- ExoPlayer (Media3) - Video playback
- Apollo GraphQL (v4.x) - Primary data source (Octopus backend)
- Normalized caching with
MemoryCacheFactory - Response-based code generation
- Client-side schema modifications
- Normalized caching with
- Ktor Client - HTTP client with custom interceptors
- Room Database - Local persistence
- Kotlin Coroutines - Asynchronous programming
- Kotlin Flow - Reactive streams
- Molecule - Reactive state management
- Arrow - Functional programming utilities (Either, etc.)
- Koin - Dependency injection (with BOMs)
- kotlinx.serialization - JSON serialization
- Timber - Logging
- Datadog - Analytics and RUM
- Firebase - Crashlytics, Analytics, Messaging
- Kotlin Multiplatform - Many modules support KMP
The project uses custom Gradle convention plugins for consistent configuration:
-
hedvig.gradle.plugin - Base plugin with:
- Feature module dependency enforcement (features can't depend on features)
- Ktlint configuration
- Common dependencies (Koin BOM, Compose BOM, OkHttp BOM)
- Auto-injection of logging and tracking
-
hedvig.android.application - Android app configuration
-
hedvig.android.library - Android library configuration
-
hedvig.jvm.library - Pure Kotlin (JVM) libraries
-
hedvig.multiplatform.library - KMP support
-
hedvig.multiplatform.library.android - used in conjuction with hedvig.multiplatform.library to add an android target to that module when it needs to have android-specific code which can not be just jvm code instead
Use this in module build.gradle.kts files:
plugins {
id("hedvig.android.library")
id("hedvig.gradle.plugin")
}
hedvig {
apollo("octopus") // Enable Apollo codegen
compose() // Enable Jetpack Compose
serialization() // Enable kotlinx.serialization
androidResources() // Enable Android resources
room(false) { /* config */ } // Enable Room database
}
dependencies {
// Use type-safe project accessors
implementation(projects.coreCommonPublic)
implementation(projects.navigationCompose)
implementation(projects.designSystemHedvig)
}Configuration in .editorconfig:
- Code style:
ktlint_official - Indent: 2 spaces
- Max line length: 120 characters
- Trailing commas: enabled
- No wildcard imports
- Function naming: Composables exempted from normal rules
Always run ./gradlew ktlintFormat before committing.
- Composable functions: PascalCase (e.g.,
FeatureScreen()) - Regular functions: camelCase
- ViewModels:
{Feature}ViewModel - Presenters:
{Feature}Presenter - Destinations:
{Feature}Destination - Use cases:
{Action}{Domain}UseCase(e.g.,GetHomeDataUseCase)
# Download schema from backend
./gradlew downloadOctopusApolloSchemaFromIntrospection
# Generate GraphQL code (happens automatically on build)
./gradlew :feature-{name}:generateApolloSourcesSchema locations:
app/apollo/apollo-octopus-public/src/main/graphql/- Main schema- Client-side schema modifications supported via build plugin
Place .graphql files in module's src/main/graphql/:
# GetHomeData.graphql
query GetHomeData {
currentMember {
id
firstName
lastName
}
}Apollo generates type-safe Kotlin code automatically.
# Run all tests
./gradlew test
# Run tests for specific module
./gradlew :feature-home:test
./gradlew :data-contract:test
# Run unit tests only
./gradlew testDebugUnitTest
# Run with coverage (if configured)
./gradlew testDebugUnitTestCoverageTest patterns:
- Unit tests:
src/test/kotlin/ - Android tests:
src/androidTest/kotlin/ - Use Turbine for testing Flows
- Use test modules for shared test utilities
GitHub Actions workflows (in .github/workflows/):
- pr.yml - PR checks (lint, test, build)
- staging.yml - Staging builds
- upload-to-play-store.yml - Production releases
- graphql-schema.yml - Schema updates
- strings.yml - Translation updates
- unused-resources.yml - Resource cleanup checks
- umbrella.yml - Comprehensive checks
- build-logic/convention/ - Gradle convention plugins
- settings.gradle.kts - Module discovery and configuration
- gradle.properties - Project properties
- .editorconfig - Code style configuration
- Create directory:
app/feature/feature-{name}/ - Add
build.gradle.kts:
plugins {
id("hedvig.android.library")
id("hedvig.gradle.plugin")
}
hedvig {
compose()
apollo("octopus") // if needed
}
dependencies {
implementation(projects.coreCommonPublic)
implementation(projects.navigationCompose)
implementation(projects.designSystemHedvig)
}- Create standard structure:
ui/,navigation/,di/ - Module will be auto-discovered by
settings.gradle.kts
- Create
-publicmodule for interfaces (KMP-compatible) - Create
-androidmodule for implementations (if needed) - Add Koin module in
di/ - Use Repository pattern for data access
- Create
.graphqlfile insrc/main/graphql/ - Enable Apollo in
build.gradle.kts:hedvig { apollo("octopus") } - Build generates type-safe Kotlin code
- Use generated query class in repository/use case
# Download latest translations
./gradlew downloadStrings
# Translations are managed via Lokalise
# String resources in app/core/core-resources/Build fails with "Cannot find schema":
./gradlew downloadOctopusApolloSchemaFromIntrospectionMissing translations:
./gradlew downloadStringsDependency resolution failures:
- Check
~/.gradle/gradle.propertieshas GitHub PAT withread:packages - See
scripts/ci-prebuild.shfor required format
Ktlint formatting errors:
./gradlew ktlintFormat # Auto-fixModules are auto-discovered via settings.gradle.kts:
- All directories under
app/withbuild.gradle.ktsare included - Micro-apps under
micro-apps/are manually included - No need to manually register new modules
- Build cache enabled via Gradle Develocity
- Configuration cache enabled (incubating)
- Type-safe project accessors for faster builds
- Parallel builds supported
- Dependency analysis plugin monitors dependency health