How the GUI Is Structured Using the Iced Framework in UAD-ng
UAD-ng implements a declarative, message-driven GUI by defining a single UadGui struct that implements Iced’s Application trait, coordinating stateful views and asynchronous Android device operations through a centralized update loop.
Universal Android Debloater Next Generation (UAD-ng) delivers its cross-platform interface through the Iced GUI library, employing an Elm-inspired architecture that separates state management from view rendering. The application centers on a central UadGui struct that manages all UI transitions and background tasks. Understanding how the GUI is structured using the Iced framework in UAD-ng reveals a modular Rust codebase where navigation, device handling, and async ADB operations remain cleanly decoupled.
Application Core: The UadGui Struct
The heart of the interface resides in src/gui.rs, where the UadGui struct implements the Application trait required by Iced. This struct stores all global UI state and implements the five mandatory lifecycle methods: new, update, view, subscription, and theme.
The UadGui struct contains:
view– An enum (View::List,View::About, orView::Settings) that determines which screen is currently active.- Sub-views – Instantiated structs for
apps_view(package list),about_view(version information), andsettings_view(configuration options). - Device state –
devices_listandselected_devicefor managing Android device connections. - Update tracking – An
UpdateStatestruct monitoring self-update status and UAD-list synchronization.
The entry point in src/main.rs bootstraps the application by calling UadGui::start(), which hands control to Iced’s event loop:
fn main() -> iced::Result {
setup_logger().expect("setup logging");
UadGui::start()
}
Message-Driven Update Architecture
All user interactions and background events in UAD-ng flow through a centralized Message enum defined in src/gui.rs. This architecture ensures that every button click, device detection, or async callback translates into a discrete message handled by the UadGui::update method.
Common message variants include:
- Navigation actions –
AppsPress,AboutPressed, andSettingsPressedswitch between views. - Device handling –
DeviceSelected,LoadDevices, andADBStateUpdatemanage ADB connections. - Async completions –
FontLoaded,GetLatestRelease, and package refresh callbacks update the UI after background work finishes.
When update receives a message, it either mutates internal state or spawns new asynchronous tasks using Task::perform. For example, navigating to the About screen triggers a fresh update check:
Message::AboutPressed => {
self.view = View::About;
self.update_state.self_update = SelfUpdateState::default();
Task::perform(
async move { get_latest_release() },
Message::GetLatestRelease,
)
}
Composable Views and Navigation
The UI is assembled from modular view components located in src/views/ and reusable widgets. Rather than monolithic rendering logic, each view module implements its own view and update methods, which UadGui orchestrates based on the current View state.
Key view modules:
src/views/list.rs– Renders the package list, loading states, and debloating actions for the selected device.src/views/about.rs– Displays version information, update status, and project links.src/views/settings.rs– Provides per-device configuration options and multi-user toggles.
The top navigation bar lives in src/widgets/navigation_menu.rs. The nav_menu function builds a dynamic horizontal row containing the device picker, refresh/reboot buttons, and view-switching shortcuts. Inside UadGui::view, the navigation container combines with the active sub-view to form the final layout:
fn view(&self) -> Element<'_, Message, Theme> {
let navigation_container = nav_menu(
&self.devices_list,
self.selected_device.clone(),
&self.apps_view,
&self.update_state.self_update,
);
let selected_device = self.selected_device.clone().unwrap_or_default();
let main_container = match self.view {
View::List => self.apps_view.view(&self.settings_view, &selected_device)
.map(Message::AppsAction),
View::About => self.about_view.view(&self.update_state).map(Message::AboutAction),
View::Settings => self.settings_view.view(&selected_device, &self.apps_view)
.map(Message::SettingsAction),
};
column![navigation_container, main_container]
.width(Length::Fill)
.align_x(Alignment::Center)
.into()
}
Initialization and Async Lifecycle
When Iced instantiates the application, UadGui::new returns both the initial state and a batch of asynchronous tasks. This pattern ensures the GUI renders immediately while background initialization proceeds concurrently.
The startup sequence in src/gui.rs uses Task::batch to parallelize:
- Font loading – Embeds and loads
icons.ttffor custom interface icons. - ADB initialization – Validates the Android Debug Bridge connection.
- Device discovery – Scans for connected devices via
get_devices_list(). - Update checks – Fetches the latest release information via
get_latest_release().
fn new() -> (Self, Task<Message>) {
(
Self::default(),
Task::batch([
font::load(include_bytes!("../../../resources/assets/icons.ttf").as_slice())
.map(Message::FontLoaded),
Task::perform(async { initial_load() }, Message::ADBSatisfied),
Task::perform(async { get_devices_list() }, Message::LoadDevices),
Task::perform(async move { get_latest_release() }, Message::GetLatestRelease),
]),
)
}
Theming and Custom Widgets
Visual consistency is enforced through src/theme.rs and src/style.rs. The UadGui::theme method maps user preferences to Iced’s Theme enum, while helper functions like button_primary and text centralize font handling and color schemes.
This styling layer allows UAD-ng to support light and dark modes seamlessly, with the Theme selection propagating automatically through Iced’s rendering pipeline whenever the user changes settings.
Summary
- Centralized Architecture – The
UadGuistruct insrc/gui.rsimplements Iced’sApplicationtrait, serving as the single source of truth for UI state. - Message Passing – All interactions flow through the
Messageenum, processed byUadGui::updateto maintain predictable state transitions. - Modular Views – Separate modules under
src/views/handle specific screens (List, About, Settings), keeping the codebase maintainable. - Async-First Design – Background operations like ADB queries and update checks use
Task::perform, preventing the interface from blocking during device communication. - Composable Navigation – The
nav_menuhelper insrc/widgets/navigation_menu.rsdynamically renders the top bar based on current device and view state.
Frequently Asked Questions
What is the Iced framework and why does UAD-ng use it?
Iced is a cross-platform GUI library for Rust inspired by the Elm Architecture, emphasizing type safety and reactive programming. UAD-ng uses Iced because it provides a declarative API where UI elements are pure functions of state, making it easier to manage complex asynchronous workflows like ADB device communication without data races or callback hell.
How does UAD-ng handle asynchronous operations without freezing the interface?
The application leverages Iced’s Task system. When UadGui::update needs to perform long-running work—such as scanning devices with get_devices_list() or downloading updates—it returns a Task::perform instead of blocking. Iced executes this task on a separate thread and delivers the result back as a new Message, keeping the main event loop responsive.
Where is the main entry point for the GUI application located?
The program starts in src/main.rs, which calls UadGui::start() to bootstrap the Iced runtime. The UadGui struct itself is defined in src/gui.rs, where it implements the Application trait and manages the entire lifecycle from initialization to shutdown.
How does view navigation work between the package list, settings, and about screens?
Navigation works through a View enum stored in UadGui. When users click navigation buttons, the update method changes self.view to View::List, View::About, or View::Settings. The view method then pattern-matches on this enum to render the appropriate sub-view component, mapping child messages back to the parent Message type using .map() transformations.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →