Universal-Debloater-Alliance/universal-android-debloater-next-generation · error
removal recommendation must be selected
Error message
removal recommendation must be selected
What it means
This is a Rust `Option::expect` panic in the `uad-gui` Iced application. `filter_package_lists` unwraps `self.selected_removal` (the currently chosen removal recommendation filter, of type `Option<Removal>`), panicking with 'removal recommendation must be selected' if it is `None`. The invariant assumes the removal dropdown always has a selection before packages are filtered; if initialization or the message that resets selections runs first, the `None` case is hit.
Solutions
- Initialize `selected_removal: Option<Removal>` to `Some(Removal::Recommended)` (or the desired default) in the view's `new`/`default` constructor instead of `None`
- Replace `.expect(...)` with `.unwrap_or(Removal::Recommended)` or an `if let Some(removal_filter) = self.selected_removal { ... }` early return that skips filtering when unset
- Ensure every code path that clears `selected_user`/view state also re-seeds `selected_removal` before triggering `filter_package_lists`
- Add a guard at the top of `filter_package_lists`: `let Some(removal_filter) = self.selected_removal else { return; };`
Example fix
// before
let removal_filter: Removal = self
.selected_removal
.expect("removal recommendation must be selected");
// after
let removal_filter: Removal = self
.selected_removal
.unwrap_or(Removal::Recommended); Defensive patterns
Strategy: validation
Validate before calling
if self.selected_removal.is_none() {
// skip filtering or apply default
return;
} Type guard
fn removal_selected(state: &ListView) -> Option<Removal> {
state.selected_removal
} Try / catch
// Rust panics are not catchable idiomatically; instead: let removal_filter = self.selected_removal.unwrap_or(Removal::Recommended);
Prevention
- Seed every Option filter field with a sensible default in the view constructor instead of None
- Prefer unwrap_or/default over expect for UI dropdown state
- Audit all state-reset paths (user switch, device change) to re-seed selections
- Run integration tests that dispatch filter messages before user interactions
When it happens
Trigger: Calling `filter_package_lists` (e.g. via the List view's filter-related Iced messages) before `self.selected_removal` is ever assigned, or after a code path resets it to `None` (e.g. on user switch or list reload) while a filter refresh is still triggered.
Common situations: App startup ordering where the packages view refreshes before the dropdown defaults are set; a refactor that makes `selected_removal` optional or resets it to `None` on a user change; a message dispatched from a background task completing after the view state was cleared.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- User must be selected
- Layout must have at least 1 child
- {e}
- There must be 1 tab after serial
- Could not write config file to disk!
AI-assisted analysis of Universal-Debloater-Alliance/universal-android-debloater-next-generation@64465c850c (2026-09-12).
Data as JSON: /api/errors/3a6bb55c8079ac34.
Report an issue: GitHub.
Appendix: source
Thrown at crates/uad-gui/src/views/list.rs:749
.spacing(10)
.align_x(Alignment::Center)
},
)
.width(900)
.height(Length::Shrink)
.max_height(700)
.style(style::Container::Background)
.into()
}
fn filter_package_lists(&mut self) {
let list_filter: UadList = self.selected_list.expect("UAD-list type must be selected");
let package_filter: PackageState = self
.selected_package_state
.expect("pack-state must be selected");
let removal_filter: Removal = self
.selected_removal
.expect("removal recommendation must be selected");
self.filtered_packages = self.phone_packages
[self.selected_user.expect("User must be selected").index]
.iter()
// we must filter the indices associated with pack-rows,
// that's why `enumerate` is before `filter`.
.enumerate()
.filter(|(_, p)| {
(list_filter == UadList::All || p.list == list_filter)
&& (package_filter == PackageState::All || p.state == package_filter)
&& (removal_filter == Removal::All || p.removal == removal_filter)
&& (self.input_value.is_empty()
|| matches_search(&p.name, &self.input_value, Some(&p.description)))
})
.map(|(i, _)| i)
.collect();
}
View on GitHub (pinned to 64465c850c)