Universal Android Debloater Configuration System: Structure and Session Persistence

The Universal Android Debloater Next Generation (UAD-ng) persists user preferences across sessions using a TOML-based configuration system that separates global UI settings from device-specific toggles, implementing automatic serialization through the Config struct in crates/uad-core/src/config.rs.

The Universal Android Debloater Next Generation (UAD-ng) relies on a robust Rust-based configuration system to maintain user preferences between application launches. This open-source Android debloating tool stores settings in a structured TOML file that survives OS reboots and binary updates. Understanding how the configuration system structures and persists data is essential for contributors and advanced users who need to customize the tool's behavior or trace device-specific settings.

Core Configuration Architecture

The Config Struct Hierarchy

At the heart of the system lies the Config struct defined in crates/uad-core/src/config.rs (lines 13-18). This struct acts as the root container with two primary fields: general (holding GeneralSettings) and devices (a vector of DeviceSettings). The hierarchical design allows the application to maintain universal UI preferences while supporting granular, per-device configurations.

Global vs Device-Specific Settings

GeneralSettings (lines 20-25) handles application-wide values including the theme name, expert mode toggle, and backup folder path. These settings apply regardless of which Android device is connected.

DeviceSettings (lines 36-44) manages connection-specific configurations such as disable_mode and multi_user_mode. Each device entry includes a unique device_id for identification. Notably, the BackupSettings field within DeviceSettings carries the #[serde(skip)] attribute, meaning runtime backup metadata persists only in memory and never writes to disk.

Persistence Mechanism and File Format

TOML Storage Location

The configuration system writes data to a lazy-initialized path defined by CONFIG_FILE in crates/uad-core/src/config.rs (lines 56-58). This resolves to <CONFIG_DIR>/config.toml, where CONFIG_DIR follows platform-specific user configuration directories (typically ~/.config/universal-android-debloater/ on Linux). The directory initializes at startup if absent.

Loading Configuration on Startup

When the application launches, the GUI calls Config::load_configuration_file() (lines 79-90). This function attempts to read and deserialize the TOML file using toml::from_str. If the file is missing or corrupted, the method automatically generates a fresh configuration with default values and writes it to disk, ensuring the application never fails due to missing config files.

Saving and Updating Settings

Persistence occurs through Config::save_device_settings() (lines 58-75). This method accepts updated DeviceSettings and GeneralSettings, merges them into the existing configuration structure, serializes the entire Config struct to TOML format, and atomically writes the content back to CONFIG_FILE. The UI triggers this routine immediately after any settings change, ensuring zero data loss between sessions.

UI Integration and Settings Views

The GUI layer in crates/uad-gui/src/views/settings.rs bridges user interactions with the configuration system. During initialization, Settings::default loads the current configuration via load_configuration_file() and populates the interface with stored values. When users toggle options like expert mode (handled in handle_expert_mode), the view updates its internal state and immediately invokes save_device_settings() to persist the change. This create-read-update cycle ensures the TOML file always reflects the latest user preferences.

Working with the Configuration System

Practical code examples for interacting with the configuration layer:

Loading the entire configuration:

use uad_core::config::Config;

// Reads from ~/.config/universal-android-debloater/config.toml
let cfg = Config::load_configuration_file();
println!("Current theme: {}", cfg.general.theme);

Reading specific global settings:

let backup_dir = Config::load_configuration_file()
    .general
    .backup_folder;
println!("Backups stored at: {}", backup_dir.display());

Updating device-specific settings:

use uad_core::config::{Config, DeviceSettings, GeneralSettings};

let mut cfg = Config::load_configuration_file();

let device = DeviceSettings {
    device_id: "ABC123".into(),
    disable_mode: true,
    multi_user_mode: false,
    backup: Default::default(),
};

// Persist changes while preserving existing general settings
cfg.save_device_settings(device, cfg.general.clone());

Summary

  • The configuration system uses a hierarchical Config struct with separate GeneralSettings and DeviceSettings components defined in crates/uad-core/src/config.rs.
  • All persistent data stores in a TOML file at <CONFIG_DIR>/config.toml, with BackupSettings explicitly excluded from serialization via #[serde(skip)].
  • load_configuration_file() handles automatic initialization and deserialization, while save_device_settings() manages atomic write-back operations.
  • The GUI in crates/uad-gui/src/views/settings.rs maintains real-time synchronization between user interface changes and disk persistence.
  • Because the system uses plain TOML, configurations survive application updates and OS reboots as long as the user configuration directory remains intact.

Frequently Asked Questions

Where does Universal Android Debloater store its configuration file?

The application stores its configuration in a TOML file located at <CONFIG_DIR>/config.toml, where CONFIG_DIR resolves to the platform-specific user configuration directory (such as ~/.config/universal-android-debloater/ on Linux or %APPDATA%\universal-android-debloater on Windows). This path is defined as a lazy-static CONFIG_FILE constant in crates/uad-core/src/config.rs.

Why are my backup settings not saving between sessions?

Backup settings use the BackupSettings struct which carries the #[serde(skip)] attribute, meaning this data is intentionally excluded from serialization. This design keeps runtime backup metadata (like available backups list and current selection) in memory only, preventing stale backup references from persisting across application restarts while keeping the configuration file focused on user preferences.

What happens if the configuration file is deleted or corrupted?

If Config::load_configuration_file() fails to find the file or encounters invalid TOML syntax, it automatically generates a fresh configuration with default values and writes it to disk. This fallback mechanism ensures the application always launches successfully, using factory defaults until the user modifies settings again through the GUI.

How does the UI ensure settings persist immediately after I change them?

The Settings view in crates/uad-gui/src/views/settings.rs implements immediate persistence by calling save_device_settings() within every toggle handler (such as handle_expert_mode). After updating the in-memory state, the UI reloads the configuration file, merges the new values, and writes the updated TOML back to disk, ensuring changes survive application crashes or system restarts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →