Keyboard Shortcuts

j / kScroll down / up
ggScroll to top
GScroll to bottom
K / /Open search
?Show this help
EscClose search / help
n / NNext / previous section
hGo to landing
dGo to docs
:Command mode — type section name

Press ? or Esc to close

Deep Dives

Form Patterns

Forms are plain Cubits — no form library. Field state lives in the Cubit; widgets are stateless and read/write through it.

State

Each form has a state holding the editable fields plus an ApiResponse for the submit status. A separate validation method returns the first error.

lib/features/auth/logic/login_cubit.dart
class LoginState {
  final String email;
  final String password;
  final SimpleApiResponse<void> submit;

  const LoginState({this.email = '', this.password = '', this.submit = const ApiResponse.initial()});

  LoginState copyWith({String? email, String? password, SimpleApiResponse<void>? submit}) =>
      LoginState(email: email ?? this.email, password: password ?? this.password, submit: submit ?? this.submit);
}

Cubit

lib/features/auth/logic/login_cubit.dart
class LoginCubit extends Cubit<LoginState> {
  LoginCubit(this._store) : super(const LoginState());

  void emailChanged(String v) => emit(state.copyWith(email: v));
  void passwordChanged(String v) => emit(state.copyWith(password: v));

  Future<void> submit() async {
    emit(state.copyWith(submit: state.submit.loading));
    final res = await _store.login(state.email, state.password);
    emit(state.copyWith(submit: res));
  }
}

Inputs

Use AppTextField for text entry and AppButton for actions. Both read design tokens — never raw colors or padding.

lib/features/auth/presentation/screens/login_screen.dart
AppTextField(
  value: state.email,
  onChanged: cubit.emailChanged,
  hint: context.l10n.t(TranslationKeys.authEmail),
);

AppButton(
  label: context.l10n.t(TranslationKeys.authSignIn),
  isLoading: state.submit.isLoading,
  onPressed: cubit.submit,
);

Validation & errors

CasePattern
Field validationInline, under the field — ErrorScope.local
Submit failureSnackbar via GlobalErrorCubit
Disabled buttonDerive from state.submit.isLoading
Rule: the screen never owns field values. If it needs to, it is a bug — move it to the Cubit.
sam's arch — A Pragmatic Flutter Architecture