Architecture

Omni is a single-module Android app built on MVVM with unidirectional data flow: Compose renders state, the BrowserViewModel owns and orchestrates it, and GeckoView does the actual browsing.

The layer map

🎨

Compose UI

Stateless screens

🧠

BrowserViewModel

State + orchestration

🦎

GeckoSession

Web engine

📡

MediaInterceptor

Stream detection

Data flows down as immutable state and up as events. Compose screens never mutate engine objects directly — they call intent methods on the ViewModel, which updates state, which recomposes the UI.

The ViewModel as orchestrator

browser/BrowserViewModel.kt (~6,000 lines) is the central state holder. It owns:

Because a single file that large is hard to navigate, the ViewModel is split into Kotlin extension files by concern:

FileResponsibility
BrowserViewModel_Session.ktSession save/restore, tab lifecycle
BrowserViewModel_Extensions.ktWebExtension install/toggle/messaging
BrowserViewModel_Passwords.ktCredential capture and autofill
BrowserViewModel_History.ktHistory read/write/search
BrowserViewModel_Bookmarks.ktBookmark tree operations
BrowserViewModel_QrScanner.ktQR decode orchestration
BrowserViewModel_FindInPage.ktFind-in-page state

State management

Omni leans on Compose’s snapshot system rather than a Redux-style store. Tab state is a value object:

data class TabState(
    val id: String,
    val url: String,
    val title: String,
    val isPrivate: Boolean,          // drives GeckoView private context
    val isDesktopMode: Boolean,
    val session: GeckoSession?,      // engine handle, not serialized
    val progress: Int
)

Mutating a tab means replacing its TabState in the SnapshotStateList; Compose recomposes only the affected rows. The GeckoSession itself is a live engine handle and is never serialized.

Navigation

Screen-level navigation uses a Compose NavHost in MainActivity with named routes — browser, settings, downloads, qr_tools, video_player/{filePath}, locker, and so on. In-browser navigation (back/forward/history) is handled by the GeckoSession and mirrored into TabState.

Media detection: dual path

Stream detection runs on two cooperating paths (see Media Hub):

Both converge on the ViewModel, which surfaces a grabber affordance in the UI.

Persistence

DataStoreEncryption
Preferences / themeDataStoreNone (non-sensitive)
History, bookmarksRoomNone
Vault contentsRoom + SQLCipherAES-256
Vault filesEncryptedFileAES-256-GCM (Keystore)
PasswordsEncrypted prefsKeystore-backed

Threading notes

Read the source doc

The repository ships docs/ARCHITECTURE.md with the authoritative, versioned diagram. This page summarizes it for the web.

Go deeper