Planned Update
This module is in the queue for a documentation refresh.
UI & State Management Component
1. Requirements Document
Overview
A set of wrappers and base classes for building consistent, reactive user interfaces. It standardizes how state is exposed to views, how side-effects (navigation, dialogs) are handled, and how common UI patterns (loading, error states) are rendered.
User Stories
- As a Developer, I want a standard
BasePagethat handles tracking and safe-area insets automatically. - As a Developer, I want to emit a "Show Snackbar" event from my Bloc without passing context around.
- As a Developer, I want a uniform way to handle loading spinners and empty states across all screens.
Functional Requirements
- Base View Model: Abstract logic for screen lifecycle (
onInit,onDispose). - View Wrapper:
BaseView<T>widget that auto-injects the dedicated ViewModel/Bloc. - State Handling:
- Standardized states:
Initial,Loading,Success<T>,Error. - Event bus for one-off actions (Navigation, Toasts).
- Standardized states:
- UI Templates:
LoadingOverlay: Blocks interaction during async ops.ErrorWidget: Retry button and illustration standards.
2. Technical Document
Architecture
System Architecture
The UI & State Wrapper enforces a clean implementation of the MVVM/Bloc pattern. The BaseView widget acts as the glue code, ensuring that every screen has a configured ViewModel and can react to standard state changes without boilerplate `StreamBuilder` code.
API Design
Base View Usage
class ProfilePage extends BaseView<ProfileController> {
const ProfilePage({super.key});
@override
Widget buildBody(BuildContext context, ProfileController controller) {
return controller.obx(
// Success State
(state) => Text(state!.username),
// Loading State (Wrapped automatically, but optionally overridden)
onLoading: const CustomSpinner(),
// Error State
onError: (err) => RetryWidget(onRetry: controller.refresh),
);
}
}Base Controller
class ProfileController extends BaseController with StateMixin<User> {
final UserApi _api;
@override
void onInit() {
super.onInit();
fetchProfile();
}
void fetchProfile() async {
change(null, status: RxStatus.loading());
final result = await _api.getUser();
result.fold(
(user) => change(user, status: RxStatus.success()),
(err) => change(null, status: RxStatus.error(err.toString())),
);
}
}