Skip to main content
Signal Android is a multi-module Gradle project. The codebase is split across four top-level module groups — app/, feature/, lib/, and core/ — each with a distinct role.

Module structure

Each module group has a corresponding -jvm variant for modules that need to run outside Android (for example, in unit tests or server-side tooling).

Core modules

The core/ modules contain foundational code that every other module can depend on.

Library modules

The lib/ modules are standalone libraries that provide specific capabilities. Each has a companion demo/ app for isolated development and testing.
The Signal service client library. Handles communication with Signal’s backend: message sending and receiving, account management, group operations, and the Signal Protocol session store. This is the primary integration point with libsignal-client.
Abstracts in-app purchase and subscription billing. The BillingApi interface is injected through AppDependencies and has a Google Play-backed implementation for play distribution builds.
Handles local device-to-device transfer of Signal data over Wi-Fi Direct or a cable connection.
UI and business logic for Signal’s donation and subscription flows.
Contact integration with the Android contacts provider, including display name resolution and avatar loading.
Backup and restore functionality. Reads and writes Signal’s encrypted backup format.
In-app image annotation editor used in the media send flow.
Video transcoding and playback utilities built on top of ExoPlayer.
A custom paging implementation designed around Signal’s SQLCipher-backed database.
QR code scanning and generation, used for contact sharing and device linking.
Signal’s Glide integration: custom decoders for Signal media, decryption of attachment thumbnails, and Glide module registration.

Feature modules

The feature/ modules encapsulate self-contained product features that are complex enough to warrant isolation from the main app module.

Dependency injection

Signal uses a hand-rolled dependency injection pattern centered on AppDependencies, an application-scoped singleton object.
The Provider interface declares factory methods for every dependency. ApplicationDependencyProvider is the production implementation; test code can substitute a different provider. Network-related dependencies (WebSocket connections, API clients, the libsignal network layer) are grouped into a separate NetworkDependenciesModule that can be reset independently when connectivity changes — for example, when a system HTTP proxy is updated.
Module-level dependency containers follow the same pattern: CoreUtilDependencies, CoreUiDependencies, SignalGlideDependencies, and MediaSendDependencies are each initialized from AppDependencies.init().

Key patterns

ViewModel and LiveData

UI screens follow the standard Android Architecture Components pattern. ViewModel classes hold UI state and expose it through LiveData or Kotlin StateFlow. Fragments and Activities observe these streams and re-render on change. Compose-based screens use collectAsState().

Repository pattern

Business logic and data access are encapsulated in repository classes (for example, MegaphoneRepository, LiveRecipientCache). Repositories abstract over the database layer and the network layer. ViewModels call into repositories; repositories call into SignalDatabase or network API classes.

JobManager background work

Long-running or retry-able operations — message sends, attachment uploads, pre-key syncs — are modelled as Job subclasses and executed by JobManager. Jobs persist to a dedicated JobDatabase so that they survive process death. The JobManager is available as AppDependencies.jobManager.
Jobs declare their constraints (network required, not roaming, etc.) and a retry strategy. JobMigrator handles schema migrations for persisted jobs across app versions.

RxJava 3 and reactive streams

Asynchronous event streams — WebSocket state, incoming messages, recipient updates — use RxJava 3 Observable and BehaviorSubject. AppDependencies.webSocketObserver is one example: it emits WebSocketConnectionState events that the UI can observe to show connectivity indicators.

Database layer

Signal’s local database is managed by SignalDatabase, a SQLite database encrypted with SQLCipher. The database class is a singleton accessed throughout the app.
  • All tables are defined as nested objects inside SignalDatabase.
  • Queries are written in raw SQL using typed helper classes; there is no ORM.
  • Write operations that affect the UI notify observers through AppDependencies.databaseObserver, which allows repositories and ViewModels to react to data changes without polling.
  • The custom lib:paging module provides cursor-based paging directly over SQLCipher queries, avoiding the overhead of loading full result sets into memory.
Never access SignalDatabase on the main thread. Use SignalExecutors or a coroutine with Dispatchers.IO.