# How the Iced Reactive UI Framework Integrates with Core Business Logic in Universal Android Debloater

> Learn how the Iced reactive UI framework integrates with core business logic in Universal Android Debloater using the Message enum for seamless async operations and UI updates.

- Repository: [Universal-Debloater-Alliance/universal-android-debloater-next-generation](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation)
- Tags: deep-dive
- Published: 2026-06-20

---

**The Universal Android Debloater bridges its Iced-based UI and core business logic through the `Message` enum, where `Task::perform` dispatches async operations to `uad-core` functions and returns results as new messages that trigger UI re-renders.**

The Universal Android Debloater next-generation project implements a strict architectural separation between its reactive frontend and system-level operations. The codebase organizes into two primary crates: `uad-gui` contains the Iced reactive UI framework implementation, while `uad-core` encapsulates all ADB communication, device synchronization, and package state management. This article examines the specific integration patterns that connect these layers, using concrete source references from the repository.

## The Iced Application Architecture

Iced drives the application through the `Application` trait, which imposes a message-loop pattern on the `UadGui` struct defined in [`crates/uad-gui/src/gui.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/gui.rs). The framework mandates three core responsibilities: the `new` method initializes state, `update` processes incoming messages, and `view` reconstructs the widget tree from immutable state.

The entry point in [`crates/uad-gui/src/main.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/main.rs) bootstraps this loop:

```rust
fn main() -> iced::Result {
    setup_logger().expect("setup logging");
    UadGui::start()               // Starts the Iced application
}

```

`UadGui::start()` instantiates the application by calling `iced::application(UadGui::new, ...)`, which begins the cycle of message processing and UI rendering.

## Bridging UI and Core Through the Message Enum

The `Message` enum in [`crates/uad-gui/src/gui.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/gui.rs) serves as the sole contract between the interface and business logic. It defines variants that either originate from user interactions or carry data produced by core functions:

```rust
pub enum Message {
    // UI navigation
    AboutPressed, SettingsPressed, AppsPress,
    // Core interactions
    DeviceSelected(Phone),                 // User picks a device
    LoadDevices(Vec<Phone>),               // Async result from uad_core::sync
    GetLatestRelease(Result<Option<Release>, ()>),
    ADBSatisfied(bool),                    // Core health check result
    // Sub-module messages
    AboutAction(AboutMessage),
    AppsAction(AppsMessage),
    SettingsAction(SettingsMessage),
    RefreshButtonPressed, RebootButtonPressed,
    Nothing,
}

```

The `Phone` structs contained in `DeviceSelected` and `LoadDevices` originate from `uad_core::sync`, demonstrating how core domain objects flow directly into UI state. When the `update` method pattern-matches on these variants, it routes user intent to the appropriate core APIs while maintaining type safety.

## Orchestrating Async Core Operations

The UI initializes core data through Iced's `Task::perform` wrapper, which converts blocking or async Rust operations into framework-compatible tasks. Inside `UadGui::new()` at lines 92-106 of [`crates/uad-gui/src/gui.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/gui.rs), the application spawns parallel initialization tasks:

```rust
Task::batch([
    // Load the custom icon font
    font::load(include_bytes!("../../../resources/assets/icons.ttf").as_slice())
        .map(Message::FontLoaded),

    // Core: detect devices via uad_core::sync::get_devices_list
    Task::perform(async { get_devices_list() }, Message::LoadDevices),

    // Core: run first-time sync to fetch package data
    Task::perform(async { initial_load() }, Message::ADBSatisfied),

    // Core: check for application updates
    Task::perform(async move { get_latest_release() }, Message::GetLatestRelease),
])

```

Each `Task::perform` call takes an async closure executing a core function and a message constructor that wraps the return value. When `get_devices_list()` completes, Iced delivers the `Vec<Phone>` result as a `Message::LoadDevices` to the update loop, triggering a UI re-render with the populated device list.

## Handling User Actions End-to-End

The integration pattern becomes clear when tracing a user action from click to completion. Consider the **Refresh** button flow:

1. **UI Event**: The refresh button emits `Message::RefreshButtonPressed`
2. **State Update**: The `update` handler in [`gui.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/gui.rs) resets the view and forwards to the list module:
   ```rust
   Message::RefreshButtonPressed => {
       self.apps_view = AppsView::default();
       self.update(Message::AppsAction(AppsMessage::LoadUadList(true)))
   }
   ```

3. **Core Invocation**: [`crates/uad-gui/src/views/list.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-gui/src/views/list.rs) calls `uad_core::sync::initial_load()` to rebuild the package cache
4. **Command Generation**: For package state changes (disable/uninstall), the UI calls `apply_pkg_state_commands` in [`crates/uad-core/src/sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/sync.rs):
   ```rust
   let cmds = uad_core::sync::apply_pkg_state_commands(
       &core_pkg,
       wanted_state,
       selected_user,
       &phone,
   );
   ```

5. **ADB Execution**: The UI spawns `Task::perform` instances that invoke `uad_core::adb::run_adb_shell_action` for each command
6. **Result Handling**: Success or failure returns as a new `Message` variant, updating the `UadListState` and re-rendering the package list with the new status

This immutable update cycle ensures the UI always reflects the latest core state without manual synchronization logic.

## Core Modules and Their UI Integration Points

The following table maps essential `uad-core` functions to their UI integration roles:

| Core Module | Function | UI Integration Role |
|-------------|----------|---------------------|
| `uad_core::sync` | `get_devices_list` | Populates the device dropdown via `Message::LoadDevices` |
| `uad_core::sync` | `initial_load` | Refreshes the package list; triggered on startup and manual refresh |
| `uad_core::sync` | `apply_pkg_state_commands` | Generates ADB command sequences for package state changes |
| `uad_core::adb` | `run_adb_shell_action` | Executes concrete shell commands; wrapped in `Task::perform` |
| `uad_core::uad_lists` | `UadListState` | Mutable package cache that the UI reads and displays |

These functions return `Result` and `Option` types that map naturally to Iced's message variant constructors, eliminating the need for complex error translation layers.

## Summary

- **Iced's message-loop** drives the application through the `Application` trait implementation on `UadGui`
- **The `Message` enum** acts as the type-safe bridge between `uad-gui` and `uad-core`, carrying both user intents and core data payloads
- **Async core operations** execute via `Task::perform`, which converts blocking ADB calls into non-blocking UI tasks
- **Immutable state updates** ensure the view always reflects the current business logic state without side effects
- **Clean crate separation** allows the core logic to remain framework-agnostic while the UI handles Iced-specific concerns

## Frequently Asked Questions

### What role does the Message enum play in the Iced integration?

The `Message` enum defines the contract between the UI and core layers. It carries user interaction events from widgets to the update loop and transports core data—such as `Vec<Phone>` from device discovery or `bool` from ADB health checks—back to the UI state. This single type unifies all communication across the crate boundary.

### How does the UI remain responsive during ADB operations?

The application uses `Task::perform` to spawn asynchronous tasks that execute blocking ADB commands in `uad_core::adb::run_adb_shell_action`. These tasks run outside the UI thread, delivering results as new `Message` variants when complete. This prevents the interface from freezing while waiting for Android device communication.

### Which crate contains the actual Android debugging logic?

The `uad-core` crate contains all business logic, including ADB command generation in [`sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/sync.rs) and shell execution in [`adb.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/adb.rs). The `uad-gui` crate remains a thin layer that translates user actions into calls to these core functions and renders the returned data.

### How does the UadGui struct maintain synchronization with core state?

`UadGui` stores core domain objects—such as `Vec<Phone>` and references to `UadListState`—directly in its struct fields. When core functions return new data via `Message` variants, the `update` method replaces these fields with the new values. Because Iced re-renders the view after every state update, the UI automatically reflects the latest core business logic state.