Repo:
element-hq/element-x-android— Android Matrix client (Compose UI +matrix-rust-sdk).
PRs must meet these rules.
- Style enforced by Editor config (
.editorconfig). - Set "Hard wrap at" to 160 chars in Android Studio.
- We use Timber for logging. Never use
android.util.Log. - Never log secrets, passwords, keys, or user content (e.g. message bodies).
- Matrix IDs (User IDs, Room IDs, Event IDs) are safe to log.
- Default localisation:
en(en-GB strings), shared with Element X iOS via Localazy. - Never edit
localazy.xml— it is auto-generated and overwritten. - New English strings go in
temporary.xml. The core team imports these to Localazy. - Key naming:
- Cross-screen verbs:
action_(e.g.,action_copy). - Common nouns/other:
common_(e.g.,common_error). - Accessibility:
a11y_. - Screen-specific:
screen_<name>_<key>(e.g.,screen_onboarding_welcome_title). - Errors:
error_prefix. - Platform-specific:
_iosor_androidsuffix. - Placeholders: Use numbered form
%1$s,%2$d.
- Cross-screen verbs:
- Create previews for all main states of a Composable.
- Use
@PreviewsDayNightfor consistency. - Use
PreviewParameterProvider(e.g.,FooStateProvider) to provide states. - Wrap previews in
ElementPreview { ... }.
- Use sentence-style commit/PR messages (no conventional commits).
- Apply exactly one
PR-label for changelog categorization. - PR title = changelog entry — make it descriptive; no "Fixes #…" prefixes.
- Include screenshots or screen recordings for any UI changes.
- Keep PRs focused; split changes over 1000 lines.
Common Gradle tasks:
- Build:
./gradlew assembleDebug - Unit Tests:
./gradlew test - Lint:
./gradlew lint - Format:
./gradlew ktlintFormat - Update Docs TOC:
./gradlew generateDocsToc
Features follow a 3-module structure:
features/foo/api: Public interfaces and data classes.features/foo/impl: Internal implementation, Presenter, and View.features/foo/test: Test fakes and utilities.
We use Appyx for navigation and Molecule for Presenters.
| File | Purpose |
|---|---|
FooNode.kt |
Appyx Node: Handles navigation and wires the Presenter to the View. |
FooPresenter.kt |
A @Composable function that produces FooState from FooEvents. |
FooView.kt |
Stateless Composable rendering the UI from FooState. |
FooState.kt |
Data class representing the immutable UI state. |
FooEvent.kt |
Sealed interface for UI actions sent to the Presenter. |
FooStateProvider.kt |
Provides sample states for Previews and Screenshot tests. |
FooPresenterTest.kt |
Unit tests for the Presenter logic using Turbine. |
- We use Metro for DI.
- Inject via constructor parameters using
@Inject. - Use
@AssistedInjectand@AssistedFactoryfor components requiring runtime arguments (like Navigators or IDs). - Use
@ContributesBinding(AppScope::class)for singleton-like services. - Use
@ContributesNode(RoomScope::class)for Appyx Nodes.
Always prefer Compound components and tokens from libraries/compound/ module.
- Colours:
ElementTheme.colors.textPrimary,ElementTheme.colors.bgCanvasDefault. - Typography:
ElementTheme.typography.fontBodyMdRegular. - Icons: Use
CompoundIcons.IconName()(e.g.,CompoundIcons.UserProfileSolid()).
We wrap the matrix-rust-sdk to isolate the UI from the underlying SDK.
- Naming: SDK
Room→JoinedRoomorRoomInfo. - Type Mapping: Map Rust SDK types to Kotlin data classes in the
apimodule to avoid leakingMatrixRustSDKinto the UI. - Always follow Kotlin naming conventions (e.g.,
userIdinstead ofuserID).