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
Thecore/ modules contain foundational code that every other module can depend on.
Library modules
Thelib/ modules are standalone libraries that provide specific capabilities. Each has a companion demo/ app for isolated development and testing.
lib:libsignal-service
lib:libsignal-service
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.lib:billing
lib:billing
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.lib:device-transfer
lib:device-transfer
Handles local device-to-device transfer of Signal data over Wi-Fi Direct or a cable connection.
lib:donations
lib:donations
UI and business logic for Signal’s donation and subscription flows.
lib:contacts
lib:contacts
Contact integration with the Android contacts provider, including display name resolution and avatar loading.
lib:archive
lib:archive
Backup and restore functionality. Reads and writes Signal’s encrypted backup format.
lib:image-editor
lib:image-editor
In-app image annotation editor used in the media send flow.
lib:video
lib:video
Video transcoding and playback utilities built on top of ExoPlayer.
lib:paging
lib:paging
A custom paging implementation designed around Signal’s SQLCipher-backed database.
lib:qr
lib:qr
QR code scanning and generation, used for contact sharing and device linking.
lib:glide
lib:glide
Signal’s Glide integration: custom decoders for Signal media, decryption of attachment thumbnails, and Glide module registration.
Feature modules
Thefeature/ 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 onAppDependencies, an application-scoped singleton object.
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.
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 asJob subclasses and executed by JobManager. Jobs persist to a dedicated JobDatabase so that they survive process death. The JobManager is available as AppDependencies.jobManager.
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 3Observable 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 bySignalDatabase, 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:pagingmodule provides cursor-based paging directly over SQLCipher queries, avoiding the overhead of loading full result sets into memory.
