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 BasePage that 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

  1. Base View Model: Abstract logic for screen lifecycle (onInit, onDispose).
  2. View Wrapper: BaseView<T> widget that auto-injects the dedicated ViewModel/Bloc.
  3. State Handling:
    • Standardized states: Initial, Loading, Success<T>, Error.
    • Event bus for one-off actions (Navigation, Toasts).
  4. 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())),
    );
  }
}