Chuyển đến nội dung chính

Bài 7: Bloc Pattern & Cubit

Bloc pattern, Events, States. Cubit vs Bloc. BlocProvider, BlocBuilder, BlocListener, BlocConsumer. MultiBlocProvider, Bloc-to-Bloc communication. Hydrated Bloc.

💻 Lập trình — Bài 7 Bài 7: Bloc Pattern & Cubit

Flutter & Dart: Từ Cơ bản đến Nâng cao

Phần 2: Navigation & State Management

xdev.asia

1. Cubit — State Management Đơn giản

dependencies:
  flutter_bloc: ^8.1.0
  hydrated_bloc: ^9.1.0
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}

2. Bloc — Event-Driven

// Events
sealed class AuthEvent {}
class AuthLoginRequested extends AuthEvent {
  final String email;
  final String password;
  AuthLoginRequested({required this.email, required this.password});
}
class AuthLogoutRequested extends AuthEvent {}

// States
sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
  final User user;
  AuthAuthenticated(this.user);
}
class AuthError extends AuthState {
  final String message;
  AuthError(this.message);
}

// Bloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository _authRepository;

  AuthBloc(this._authRepository) : super(AuthInitial()) {
    on<AuthLoginRequested>(_onLogin);
    on<AuthLogoutRequested>(_onLogout);
  }

  Future<void> _onLogin(AuthLoginRequested event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    try {
      final user = await _authRepository.login(event.email, event.password);
      emit(AuthAuthenticated(user));
    } catch (e) {
      emit(AuthError(e.toString()));
    }
  }

  Future<void> _onLogout(AuthLogoutRequested event, Emitter<AuthState> emit) async {
    await _authRepository.logout();
    emit(AuthInitial());
  }
}

3. BlocProvider, BlocBuilder, BlocListener

// Provide
MultiBlocProvider(
  providers: [
    BlocProvider(create: (_) => AuthBloc(authRepo)),
    BlocProvider(create: (_) => ThemeBloc()),
  ],
  child: const MyApp(),
);

// Build
BlocBuilder<AuthBloc, AuthState>(
  builder: (context, state) => switch (state) {
    AuthLoading() => const CircularProgressIndicator(),
    AuthAuthenticated(:final user) => Text('Welcome ${user.name}'),
    AuthError(:final message) => Text(message),
    _ => const LoginForm(),
  },
);

// Listen — side effects
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthError) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.message)));
    }
  },
  child: const LoginForm(),
);

// BlocConsumer = Builder + Listener
BlocConsumer<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthAuthenticated) {
      context.go('/home');
    }
  },
  builder: (context, state) {
    return ElevatedButton(
      onPressed: state is AuthLoading ? null : () {
        context.read<AuthBloc>().add(AuthLoginRequested(email: email, password: password));
      },
      child: state is AuthLoading ? const CircularProgressIndicator() : const Text('Login'),
    );
  },
);

4. Hydrated Bloc — Persist State

class ThemeBloc extends HydratedCubit<ThemeMode> {
  ThemeBloc() : super(ThemeMode.system);

  void toggle() => emit(state == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark);

  @override
  ThemeMode fromJson(Map<String, dynamic> json) =>
      ThemeMode.values[json['mode'] as int];

  @override
  Map<String, dynamic> toJson(ThemeMode state) => {'mode': state.index};
}

Bài tiếp theo: Forms, Validation & Theming.