Skip to main content
The ProtonVPN Android app follows a layered MVVM architecture enforced with Hilt dependency injection. Each layer has a single responsibility and communicates with adjacent layers through well-defined interfaces and reactive streams.

Architectural layers

UI layer

Built with Jetpack Compose. Screens observe StateFlow and Flow from ViewModels. No business logic lives here.

ViewModels

Android ViewModel subclasses scoped to navigation destinations. Transform domain state into UI state and delegate user actions to use cases.

Use cases

Focused, single-responsibility classes. Examples: GetSmartProtocols, SettingsForConnection, ObserveExcludedLocations. Injected directly into ViewModels and managers.

Repositories

Own the authoritative copy of a domain model (servers, certificates, recents). Merge API responses with locally persisted data.

Data sources

Room database (AppDatabase), Retrofit API client (ProtonApiRetroFit), SharedPreferences, and the live VPN service state emitted by VpnBackend.

Proton Core

The me.proton.core.* libraries provide account management, authentication, networking, feature flags, telemetry, and more. The app binds Core interfaces in di/AppDatabaseModule.kt.

Async model: coroutines and Flow

All asynchronous work uses Kotlin coroutines. A single CoroutineScope (a MainScope) is provided by Hilt and injected wherever long-lived work is launched:
State is propagated as StateFlow or Flow. ViewModels collect these flows and expose them as StateFlow<UiState> to composables. The VPN state machine itself is built on MutableStateFlow:
Prefer stateIn(scope, SharingStarted.Eagerly, initial) when you need to convert a cold Flow into a hot StateFlow for the UI layer.

Dependency injection with Hilt

The app uses Hilt (dagger.hilt.*) throughout. All Hilt modules are installed into SingletonComponent and live in app/src/main/java/com/protonvpn/android/di/: @Singleton is used for stateful services (VpnConnectionManager, VpnStateMonitor, CertificateRepository, ServerManager2). Use cases are typically @Reusable or unscoped.

Room database

A single AppDatabase (version 54 as of this writing) consolidates all entities. It extends both VPN-specific database interfaces and Proton Core database interfaces:
VPN-specific entities include:
  • RecentConnectionEntity / UnnamedRecentIntentEntity — recent connections
  • ProfileEntity — user-created connection profiles
  • ExcludedLocationEntity — split-tunneling excluded locations
  • VpnUser — cached VPN user data
  • PeriodicCallInfo — periodic update scheduling state
DAOs are provided through AppDatabaseDaoModule and injected into repositories.

Module organisation

Proton Core integration

The app links against several me.proton.core.* artifacts:
  • core-account — account entity, session management
  • core-auth — sign-in, SSO
  • core-network — OkHttp setup, DoH, API error handling
  • core-feature-flag — Unleash feature flags (fetched and cached in Room)
  • core-telemetry — event batching and upload
  • core-user / core-usersettings — user and settings entities
  • core-notification / core-push — notification infrastructure
  • core-observability — metric event funneling
Core database interfaces are bound to AppDatabase in AppDatabaseBindsModule, so Core repositories obtain their DAOs without knowing about the concrete database class.