Running the formatter
The project uses ktlint version1.5.0 for Kotlin formatting. A top-level Gradle task applies formatting across all modules — including the build-logic included build:
ktlintFormat on every subproject and on the build logic plugins and tools. Run it before every commit.
./gradlew format modifies files in place. After running it, stage any changed files before committing.Setting up lefthook
Lefthook enforces formatting automatically as a git pre-push hook. The project ships alefthook.yml configuration at the repository root:
./gradlew format and then checks whether any files were modified. If formatting changes are detected, the push is blocked and you must commit the changes before pushing again.
Installing lefthook
1
Install lefthook
Follow the installation instructions for your operating system. On macOS with Homebrew:
2
Enable the hooks
From the repository root, run:This installs the git hook defined in
lefthook.yml into .git/hooks/.3
Override locally if needed
Create a
lefthook-local.yml file (it is gitignored) to override settings for your local environment. See the lefthook documentation for details.Kotlin conventions
New code should be written in Kotlin. Follow standard Kotlin idioms and the official Kotlin coding conventions, with the following project-specific notes:-
Null safety: prefer non-null types. Use
?.and?:rather than manual null checks where possible. -
objectfor singletons: application-scoped singletons use Kotlinobjectdeclarations (seeAppDependencies). -
Lazy initialization: use
by lazy { ... }for singletons that should be initialized on first access. - Extension functions: use them to add behavior to existing types rather than creating utility classes with static methods.
-
Coroutines: prefer coroutines with
Dispatchers.IOfor background work over raw thread pools where new code allows it. -
@JvmStaticand@JvmField: annotate members ofobjectclasses that are called from Java with@JvmStaticso they compile to static methods. -
JVM target is set in
app/build.gradle.kts:
Java conventions
Existing code inorg.thoughtcrime.securesms is largely Java. When modifying Java files:
- Follow the existing code style in the file you are editing.
- Prefer annotating parameters and return types with
@NonNull/@Nullable. - Use
@VisibleForTestingwhen relaxing access modifiers for test purposes. - Do not introduce new Java files for new features — write Kotlin instead.
Import organization
ktlint enforces import ordering automatically. Do not manually reorder imports; running./gradlew format will sort them correctly. The general order after formatting is:
- Android imports
- Third-party library imports
org.signal.*importsorg.thoughtcrime.securesms.*importsorg.whispersystems.*imports
Logging
Signal uses a custom logging wrapper. Do not useandroid.util.Log directly.
LogNotSignal, LogNotAppSignal, and LogTagInlined enforce this at build time and are set to error severity in lint.xml.
Lint rules
The project’slint.xml configures severity overrides for Android lint checks. Key rules to be aware of:
StopShip (fatal)
StopShip (fatal)
Any
STOPSHIP comment in a .kt, .kts, .java, or .xml file will cause both the checkStopship Gradle task and lint to fail. Do not leave STOPSHIP markers in code you intend to merge.HardcodedText (error)
HardcodedText (error)
All user-visible strings must be defined in string resources. Hardcoded text in layouts fails lint.
LogNotSignal / LogNotAppSignal / LogTagInlined (error)
LogNotSignal / LogNotAppSignal / LogTagInlined (error)
Direct use of
android.util.Log or inlined log tags is an error. Use Signal’s logging utilities and declare a private static final String TAG (Java) or private val TAG (Kotlin) constant.StringFormatMatches (error)
StringFormatMatches (error)
Mismatched format string arguments cause runtime crashes. This check is set to
error to catch them at build time.MissingTranslation (ignore)
MissingTranslation (ignore)
Missing translations are expected during development — the
excludeNonTranslatables task removes untranslated strings from non-English resource files before release.RestrictedApi (error)
RestrictedApi (error)
Usage of restricted APIs is an error with narrow path-based exceptions for legacy files. New code must not use restricted APIs.
