This file provides guidance to Agents when working with code in this repository. See https://agents.md/ for details about this file type.
Wire for Android is a Jetpack Compose messaging application for the Wire platform. It handles authentication, messaging, voice/video calling, end-to-end encryption, and enterprise features. All business logic is provided by the Kalium Kotlin Multiplatform SDK, which is included as a git submodule.
Requirements: JDK 21, Android SDK, Android NDK
First-time setup:
git submodule update --init --recursive
# Copy local.properties to kalium/ — the IDE will NOT create it automatically
cp local.properties kalium/local.propertiesKey Technologies:
- Kotlin 2.3.10 with Android Gradle Plugin 9.0.1
- Jetpack Compose + Material 3 (Compose BOM 2026.01.01)
- Hilt 2.59.1 for dependency injection
- Kotlin Coroutines 1.10.2 + Flow for async/reactive
- Coil 3 for image loading
- Compose Destinations 2.3.0 (KSP-based navigation)
- Paging 3 for paginated lists
- Kalium SDK (submodule) for all messaging, encryption, and data logic
- Detekt 1.23.8 for static analysis, Kover for code coverage
# Submodule management
git submodule update --init --recursive # First-time init
git submodule update --remote --merge # Update to latest
# Compile and assemble
./gradlew compileApp # Compile only
./gradlew assembleApp # Assemble APK
./gradlew runApp # Assemble and run on connected device
# Makefile shortcuts
make assemble/staging-debug # Assemble staging debug APK
make install/staging-debug # Install staging debug APK
# Testing
./gradlew runUnitTests # All unit tests
./gradlew testCoverage # Unit tests + Kover XML coverage report
./gradlew runAcceptanceTests # UI/acceptance tests (requires connected device or emulator)
./gradlew :app:testDevDebugUnitTest # Single module unit tests (app, dev flavor)
./gradlew :<module>:test # Any individual module
# Code quality
./gradlew staticCodeAnalysis # Run detektAll (must pass before finishing)
# Screenshot tests (app module only)
./gradlew :app:validateStagingDebugScreenshotTestEach flavor has a different icon background color for easy identification.
| Flavor | Color | Backend | Logging | Description |
|---|---|---|---|---|
dev |
Red | Wire Staging | ✅ | Bleeding edge, for feature development |
staging |
Orange | Wire Staging | ✅ | QA testing; mirrors prod feature flags + dev tools |
internal |
Green | Wire Prod | ✅ | Internal use (limited usage) |
beta |
Blue | Wire Prod | ✅ | Dogfood app for Wire employees |
prod |
White | Wire Prod | ✗ | Public release on Play Store |
fdroid |
White | Wire Prod | ✗ | FOSS build published on F-Droid, no closed-source code |
Note: Logs are never uploaded on
prod/fdroid. Sensitive content (message body, encryption keys) is never logged in any flavor.
For flavor customization details, see CUSTOMIZATION.md and default.json.
Modules are auto-discovered by settings.gradle.kts from the app/, core/, features/, and tests/ directories.
app/ — Main Android application
└── src/
├── main/ — Shared sources
├── <flavor>/ — Flavor-specific overrides (dev, staging, prod, fdroid…)
├── private/ — Internal flavor sources (Datadog logger)
├── public/ — Public flavor sources
├── nonfree/ — Non-FOSS sources (GMS, Firebase)
├── foss/ — FOSS-only sources
├── test/ — Unit tests
├── androidTest/ — Instrumentation tests
└── screenshotTest/ — Compose screenshot tests
core/
├── di/ — Hilt DI modules shared across modules
├── ui-common/ — Shared Compose UI components, theming, and utilities
├── navigation/ — Navigation infrastructure and graph setup
├── analytics/ — Analytics tracking (enabled/disabled variants)
├── media/ — Audio/video media utilities
└── notification/ — Push notification handling
features/
├── cells/ — Wire Cells file storage and collaboration
├── meetings/ — Voice/video calling UI
├── sync/ — Data synchronization
├── sketch/ — Drawing/sketch feature
└── template/ — Starter template for new feature modules
kalium/ — KMP messaging SDK (git submodule — see kalium/AGENTS.md)
build-logic/ — Gradle convention plugins (wire-android-application, wire-android-library, wire-hilt, etc.)
buildSrc/ — Build script helpers and legacy script plugins
tests/ — Shared test infrastructure
benchmark/ — Benchmarking module
docs/adr/ — Architecture Decision Records
The project enforces a strict layered dependency direction:
kalium (submodule)
↑
core:* (di, ui-common, navigation, analytics, media, notification)
↑
features:* (cells, meetings, sync, sketch)
↑
app
Rules:
core:*modules must NOT depend onfeatures:*orappfeatures:*may depend oncore:*and Kalium, but NOT onappapporchestrates everything and may depend on any module- Never invert the dependency direction
- Do not add cross-feature dependencies — route shared logic through
core:*or Kalium
Pattern: MVVM with Clean Architecture principles
UI Layer:
- Jetpack Compose screens annotated with
@Destination(Compose Destinations) @HiltViewModelViewModels injected viahiltViewModel()composable- UI state exposed as
StateFlow, one-shot effects asSharedFlow - Navigation handled through
core:navigationand Compose Destinations
Domain / Data Layer:
- All business logic lives in the Kalium submodule (use cases, repositories, encryption)
- ViewModels call Kalium use cases directly — no reimplementation of domain logic in this repo
- See kalium/AGENTS.md for the full Kalium architecture
Data Flow:
Compose UI → ViewModel (StateFlow/SharedFlow) → Kalium Use Case → Kalium Repository → Kalium Network/DB
Compose Destinations:
- Each screen file is annotated with
@Destination - Navigation graphs are assembled per-module and merged in
core:navigation - KSP generates type-safe nav argument wrappers
Frameworks:
- JUnit 5 (Jupiter) with
@ParameterizedTestsupport (see ADR-0003) - MockK for mocking (
@MockK,coEvery,every) - Turbine for Flow testing (
flow.test { ... }) - Robolectric for Android unit tests
- UI Automator for acceptance/integration tests on device (see ADR-0007)
- Compose screenshot testing via Android Gradle Plugin experimental support
Test infrastructure:
CoroutineTestExtension— sets upTestCoroutineDispatcher; required for all ViewModel tests- Path:
app/src/test/kotlin/com/wire/android/config/CoroutineTestExtension.kt
- Path:
SnapshotExtension— ComposeTextFieldStatesnapshot testing- Path:
app/src/test/kotlin/com/wire/android/config/SnapshotExtension.kt
- Path:
ViewModel test pattern — Arrangement Builder:
@ExtendWith(CoroutineTestExtension::class)
class SomeViewModelTest {
@Test
fun givenSomeCondition_whenActionHappens_thenExpectedResult() = runTest {
val (arrangement, viewModel) = Arrangement()
.withSomeBehavior(Result.Success)
.arrange()
viewModel.onSomeAction()
assertEquals(ExpectedState, viewModel.state.value)
}
private class Arrangement {
@MockK
lateinit var someUseCase: SomeUseCase
init {
MockKAnnotations.init(this, relaxUnitFun = true)
}
fun withSomeBehavior(result: SomeUseCase.Result) = apply {
coEvery { someUseCase() } returns result
}
fun arrange() = this to SomeViewModel(someUseCase)
}
}Test naming convention: givenX_whenY_thenZ
fun givenUserIsLoggedOut_whenLoginIsCalled_thenNavigateToHome()
fun givenNetworkErrorOccurs_whenSendingMessage_thenShowErrorState()Test source sets:
src/test/kotlin/— JVM unit tests (Robolectric for Android classes)src/androidTest/kotlin/— Instrumented tests (UI Automator, Espresso)src/screenshotTest/kotlin/— Compose screenshot tests
@HiltViewModelwith constructor injection — no manual ViewModel factoriesStateFlowfor UI state,SharedFlowfor one-shot navigation/side-effect events@Destinationannotation on every Compose screen for type-safe navigationkotlinx-datetime(Instant,LocalDateTime) for all date/time valueskotlinx.serializationwith@Serializablefor data serialization- Do NOT expose Kalium
Either<Failure, Success>types to the UI layer — map to concrete UI state sealed classes - Feature flags controlled via
BuildConfigfields set per flavor - Flavored source sets follow the pattern:
src/<flavor>/kotlin/for flavor-specific implementations
- Missing submodule: Always run
git submodule update --init --recursiveafter cloning. Without this the build fails silently or with cryptic errors. - Missing
kalium/local.properties: The IDE does not create this file automatically. Copy it from the project root:cp local.properties kalium/local.properties. - Stale submodule reference: After pulling, run
git submodule update --remote --mergeif the build references missing Kalium symbols. kalium.providerCacheScopemust beGLOBAL: Set ingradle.properties. Do not change it — Kalium has no default and requires consumers to set it explicitly.- F-Droid flavor must stay FOSS: Never add Google/Firebase/GMS dependencies that are reachable from the
fdroidflavor. Use source set separation (src/nonfree/vssrc/foss/). - Screenshot tests module opt-in: A module must set
experimentalProperties["android.experimental.enableScreenshotTest"] = truein itsbuild.gradle.ktsbefore screenshot tests will run. - Protobuf conflict: The
appmodule excludesprotobuf-javain favor ofprotobuf-lite. Do not re-addprotobuf-javaas a dependency. libsodium.soconflict: Resolved viajniLibs.pickFirsts.add("**/libsodium.so"). Do not remove this packaging rule.
- Never read secrets in the codebase.
- API keys, passwords, tokens should always be ignored and not processed.
- Allowed Without Prompting:
- Read any source file.
- Run linters, formatters, or type checkers on single files.
- Run unit tests on specific test files or modules.
- Require Approval First:
- Adding a new library/dependency.
- Changing the dependencies between modules.
- Git operations (
git push,git commit). - Deleting files or directories.
- Running full build or acceptance/E2E tests.
- Modifying CI/CD configuration and scripts.
- Introducing a new architectural pattern or design convention.
- Any change that touches the
fdroidflavor's dependency graph.
Adhere to the following guidelines for each session:
- If the code cannot be tested, it is not a valid solution.
- ViewModels must be testable via the Arrangement Builder pattern without an Android runtime.
- Run
./gradlew :<module>:testfor each module you modify before finishing. - All new and modified code paths must be covered by tests.
- When fixing a bug, add a regression test.
- ViewModels:
@HiltViewModel, constructor-injected use cases from Kalium, state viaStateFlow. - Screens:
@Destination-annotated Composables, receive state from ViewModel, emit events back. - UI state: Define a dedicated
sealed classordata classper screen — never exposeEitherto the UI. - DI: Hilt modules in
core:di; feature-specific modules in the feature's own module. - New feature modules: Copy from
features/template/and register insettings.gradle.kts.
- Dependency direction:
kalium → core → features → app. Never invert. - Do not add direct
appdependencies fromcore:*orfeatures:*. - Do not create cross-feature dependencies — put shared logic in
core:*or Kalium. - Check existing module
build.gradle.ktsfiles before adding a new inter-module dependency.
- Adding a new library/dependency or introducing a new pattern requires an ADR in
docs/adr/. - Name it sequentially:
docs/adr/XXXX-kebab-case-title.md(see0000-template-lightway-adr.md). - Get the ADR approved before implementing the change.
- Focus on narrow, well-defined tasks.
- Require approval before: adding a dependency, changing module dependencies, introducing a new pattern, touching CI/CD, deleting files, running full builds, or modifying the
fdroidflavor's dependency graph.
./gradlew staticCodeAnalysis— must pass with zero issues on all changed files.