Skip to main content
Signal Android enforces consistent code style through automated formatting and lint checks. Before submitting a pull request, make sure your changes pass both.

Running the formatter

The project uses ktlint version 1.5.0 for Kotlin formatting. A top-level Gradle task applies formatting across all modules — including the build-logic included build:
This task runs 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 a lefthook.yml configuration at the repository root:
When you push, lefthook runs ./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.
  • object for singletons: application-scoped singletons use Kotlin object declarations (see AppDependencies).
  • 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.IO for background work over raw thread pools where new code allows it.
  • @JvmStatic and @JvmField: annotate members of object classes that are called from Java with @JvmStatic so they compile to static methods.
  • JVM target is set in app/build.gradle.kts:

Java conventions

Existing code in org.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 @VisibleForTesting when 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:
  1. Android imports
  2. Third-party library imports
  3. org.signal.* imports
  4. org.thoughtcrime.securesms.* imports
  5. org.whispersystems.* imports
Wildcard imports are not allowed.

Logging

Signal uses a custom logging wrapper. Do not use android.util.Log directly.
The custom lint rules LogNotSignal, LogNotAppSignal, and LogTagInlined enforce this at build time and are set to error severity in lint.xml.

Lint rules

The project’s lint.xml configures severity overrides for Android lint checks. Key rules to be aware of:
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.
All user-visible strings must be defined in string resources. Hardcoded text in layouts fails lint.
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.
Mismatched format string arguments cause runtime crashes. This check is set to error to catch them at build time.
Missing translations are expected during development — the excludeNonTranslatables task removes untranslated strings from non-English resource files before release.
Usage of restricted APIs is an error with narrow path-based exceptions for legacy files. New code must not use restricted APIs.

Running lint

Run ./gradlew qa locally before opening a pull request. It mirrors the checks that run in CI.