Form management is one of the most frequent daily challenges in modern Flutter development. Whether building a simple login screen, a settings form, or an enterprise multi-step checkout wizard, developers constantly wrestle with a familiar set of questions:
- How do I keep input validation reactive without triggering full-screen widget rebuilds?
- Should validation error messages live inside my state model, or be evaluated dynamically?
- When should I use a simple
Cubitvs. a reifiedBlocevent pipeline?
In this article, we’ll explore how BlocSignal—which bridges traditional BLoC event architecture with Rody Davis’s signals primitives—solves these problems elegantly. We’ll look at the fundamental rule of Primary vs. Derived State, compare three distinct form architectural patterns, and provide a decision matrix to help you pick the right approach for your next Flutter project.
1. Primary vs. Derived State with Signals
Before diving into event dispatching or bloc patterns, let's examine the single biggest source of form bugs in Flutter applications: State Duplication.
The Anti-Pattern: Storing Validation Errors in State
In classic state management implementations, developers often define form state models that look like this:
// ❌ ANTI-PATTERN: Storing derived validation fields in state
class BadLoginFormState {
final String email;
final String password;
final String? emailError; // ⚠️ Redundant derived state!
final String? passwordError; // ⚠️ Redundant derived state!
final bool isValid; // ⚠️ Redundant derived state!
final bool isSubmitting;
}
Whenever the email changes, the developer must manually run validation checks, update emailError, recalculate isValid, and call copyWith(...).
This approach creates several problems:
Desynchronization: It is easy to updateemailbut forget to recalculateisValidor resetemailError.
Boilerplate: Every state mutation requires repetitive validation logic scattered across event handlers.
Redundant Rebuilds: Emitting new state objects just to update an error string can trigger unneeded widget renders.
The Solution: Derived State via computed() Signals
With BlocSignal, state holds Primary State only—the actual single source of truth:
// ✅ RECOMMENDED: Pure Primary State
@immutable
class LoginFormState {
const LoginFormState({
this.email = '',
this.password = '',
this.isSubmitting = false,
this.isSuccess = false,
});
final String email;
final String password;
final bool isSubmitting;
final bool isSuccess;
}
Validation logic is defined outside the state class as reactive computed() signals directly inside your CubitSignal or BlocSignal:
class LoginFormBloc extends BlocSignal<LoginFormEvent, LoginFormState> {
LoginFormBloc() : super(initialState: const LoginFormState());
/// Derived Signal: Evaluates email error lazily & reactively
late final ReadonlySignal<String?> emailError = computed(() {
final email = stateValue.email;
if (email.isEmpty) return null;
if (!email.contains('@') || !email.contains('.')) {
return 'Please enter a valid email address';
}
return null;
});
/// Derived Signal: Evaluates password error lazily & reactively
late final ReadonlySignal<String?> passwordError = computed(() {
final pass = stateValue.password;
if (pass.isEmpty) return null;
if (pass.length < 6) return 'Password must be at least 6 characters';
return null;
});
/// Derived Signal: Overall form validity
late final ReadonlySignal<bool> isValid = computed(() {
final s = stateValue;
return s.email.isNotEmpty &&
s.password.isNotEmpty &&
emailError.value == null &&
passwordError.value == null;
});
}
💡 Production Tip: The basic string checks shown in these educational snippets serve as simple examples. In production applications, use a dedicated validation package such as
💡 Form Validation Example: examples/flutter_form_validation
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR