0
0
Lập trình
Admin Team
Admin Teamtechmely

Nắm Vững BLoC trong Flutter: Hướng Dẫn Thực Tế

Đăng vào 6 tháng trước

• 10 phút đọc

Giới thiệu

Quản lý trạng thái là một phần quan trọng trong bất kỳ ứng dụng Flutter nào. Khi ứng dụng của bạn phát triển, việc quản lý cách dữ liệu được truyền tải có thể trở nên phức tạp. Đây là lúc một mô hình kiến trúc vững chắc xuất hiện, và đối với nhiều nhà phát triển Flutter, BLoC là câu trả lời.

Mô hình BLoC (Business Logic Component) giúp tách biệt logic kinh doanh của ứng dụng khỏi giao diện người dùng (UI), dẫn đến mã nguồn dễ dàng hơn để kiểm tra, bảo trì và mở rộng. Nhưng chỉ biết về BLoC thôi thì chưa đủ. Sử dụng nó hiệu quả mới là điều tạo nên sự khác biệt. Hướng dẫn này sẽ đưa bạn qua một ví dụ thực tế, đầy đủ mã nguồn để giúp bạn làm chủ mô hình BLoC trong Flutter.

BLoC là gì và Tại sao Nên Sử Dụng

Về cơ bản, mô hình BLoC là một cách để xử lý trạng thái trong một ứng dụng. Nó hoạt động bằng cách tiếp nhận các sự kiện từ UI, xử lý chúng trong lớp logic kinh doanh, và phát ra các trạng thái mới trở lại UI. UI sau đó sẽ tự xây dựng lại dựa trên trạng thái mới.

Lợi ích chính của việc sử dụng BLoC

  • Tách biệt các mối quan tâm: Mã UI của bạn chỉ cần quan tâm đến việc hiển thị các trạng thái và phát các sự kiện. Logic kinh doanh được chứa hoàn toàn trong BLoC, làm cho nó độc lập với UI.
  • Khả năng kiểm tra: Khi logic kinh doanh được tách biệt, bạn có thể viết các bài kiểm tra đơn vị cho các BLoC mà không cần phải hiển thị bất kỳ thành phần UI nào.
  • Khả năng mở rộng: Khi ứng dụng của bạn phát triển, BLoC cung cấp một cách rõ ràng và dễ dự đoán để quản lý trạng thái, ngăn chặn mã nguồn của bạn trở nên lộn xộn.

Ví dụ BLoC Hoàn Chỉnh: Quy Trình Xác Thực

Hãy cùng xây dựng một tính năng xác thực đơn giản để xem tất cả các phần của BLoC kết hợp với nhau. Chúng ta sẽ đề cập đến việc định nghĩa các sự kiện và trạng thái, triển khai BLoC, và kết nối nó với UI.

Bước 1: Định Nghĩa Các Trạng Thái

Đầu tiên, chúng ta định nghĩa các trạng thái có thể có của tính năng xác thực. Một cách tiếp cận tốt là sử dụng một lớp cơ sở trừu tượng.

dart Copy
import 'package:equatable/equatable.dart';

abstract class AuthState extends Equatable {
  const AuthState();

  @override
  List<Object> get props => [];
}

// Trạng thái ban đầu, trước bất kỳ tương tác nào của người dùng
class AuthInitial extends AuthState {}

// Trạng thái khi xác thực đang diễn ra
class AuthLoading extends AuthState {}

// Trạng thái khi người dùng đã xác thực thành công
class AuthSuccess extends AuthState {
  final String userToken; // Dữ liệu ví dụ
  const AuthSuccess({required this.userToken});

  @override
  List<Object> get props => [userToken];
}

// Trạng thái khi xác thực thất bại
class AuthFailure extends AuthState {
  final String error;
  const AuthFailure({required this.error});

  @override
  List<Object> get props => [error];
}

Bước 2: Định Nghĩa Các Sự Kiện

Tiếp theo, chúng ta định nghĩa các sự kiện mà UI có thể gửi đến BLoC để kích hoạt các thay đổi trạng thái.

dart Copy
import 'package:equatable/equatable.dart';

abstract class AuthEvent extends Equatable {
  const AuthEvent();

  @override
  List<Object> get props => [];
}

// Sự kiện được kích hoạt khi người dùng cố gắng đăng nhập
class AuthLoginRequested extends AuthEvent {
  final String email;
  final String password;

  const AuthLoginRequested({required this.email, required this.password});

  @override
  List<Object> get props => [email, password];
}

// Sự kiện được kích hoạt khi người dùng đăng xuất
class AuthLogoutRequested extends AuthEvent {}

Bước 3: Triển Khai BLoC

Bây giờ chúng ta tạo AuthBloc chính nó. Nó sẽ lắng nghe các AuthEvent và phát ra các AuthState để phản hồi. Đây là nơi logic kinh doanh của bạn sống.

dart Copy
import 'package:flutter_bloc/flutter_bloc.dart';
// Nhập các tệp sự kiện và trạng thái của bạn

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  // Thông thường, bạn sẽ tiêm một kho dữ liệu hoặc dịch vụ ở đây
  // final AuthRepository authRepository;

  AuthBloc() : super(AuthInitial()) {
    on<AuthLoginRequested>(_onLoginRequested);
    on<AuthLogoutRequested>(_onLogoutRequested);
  }

  Future<void> _onLoginRequested(
    AuthLoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(AuthLoading());
    try {
      // Giả lập một cuộc gọi mạng
      await Future.delayed(const Duration(seconds: 2));

      if (event.email == 'test@test.com' && event.password == 'password') {
        const token = 'fake-jwt-token';
        emit(const AuthSuccess(userToken: token));
      } else {
        throw 'Thông tin xác thực không hợp lệ';
      }
    } catch (e) {
      emit(AuthFailure(error: e.toString()));
    }
  }

  Future<void> _onLogoutRequested(
    AuthLogoutRequested event,
    Emitter<AuthState> emit,
  ) async {
    // Xóa phiên người dùng, v.v.
    emit(AuthInitial());
  }
}

Bước 4: Kết Nối BLoC với UI

Để sử dụng AuthBloc, trước tiên chúng ta cần cung cấp nó cho cây widget của mình bằng cách sử dụng BlocProvider. Điều này thường được thực hiện ở gốc của ứng dụng hoặc ở đầu màn hình tính năng liên quan.

dart Copy
// main.dart hoặc một điểm nhập tương tự
runApp(
  BlocProvider(
    create: (context) => AuthBloc(),
    child: const MyApp(),
  ),
);

Bây giờ, trong UI của chúng ta, chúng ta có thể sử dụng BlocBuilder để lắng nghe các thay đổi trạng thái và xây dựng lại các widget, và BlocListener cho các hành động một lần như điều hướng hoặc hiển thị snackbar. Một widget tuyệt vời kết hợp cả hai là BlocConsumer.

Hãy xem trong một trang đăng nhập:

dart Copy
class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    final emailController = TextEditingController();
    final passwordController = TextEditingController();

    return Scaffold(
      appBar: AppBar(title: const Text('Đăng Nhập')),
      body: BlocConsumer<AuthBloc, AuthState>(
        listener: (context, state) {
          // Listener cho các tác động phụ
          if (state is AuthFailure) {
            ScaffoldMessenger.of(context)
              ..hideCurrentSnackBar()
              ..showSnackBar(
                SnackBar(content: Text(state.error)),
              );
          } else if (state is AuthSuccess) {
            // Điều hướng đến màn hình chính khi thành công
            Navigator.of(context).pushReplacementNamed('/home');
          }
        },
        builder: (context, state) {
          // Builder để xây dựng lại UI
          if (state is AuthLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextField(
                  controller: emailController,
                  decoration: const InputDecoration(labelText: 'Email'),
                ),
                TextField(
                  controller: passwordController,
                  obscureText: true,
                  decoration: const InputDecoration(labelText: 'Mật Khẩu'),
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () {
                    // Thêm sự kiện đăng nhập vào BLoC
                    context.read<AuthBloc>().add(
                          AuthLoginRequested(
                            email: emailController.text,
                            password: passwordController.text,
                          ),
                        );
                  },
                  child: const Text('Đăng Nhập'),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

Ví dụ này cho thấy toàn bộ chu trình:

  1. UI phát đi một sự kiện AuthLoginRequested khi nút được nhấn.
  2. AuthBloc nhận sự kiện, phát ra một trạng thái AuthLoading.
  3. BlocConsumer xây dựng lại UI để hiển thị một chỉ báo tải.
  4. BLoC hoàn thành logic của nó và phát ra hoặc AuthSuccess hoặc AuthFailure.
  5. Listener của BlocConsumer kích hoạt một điều hướng hoặc snackbar, và builder cập nhật UI nếu cần thiết.

Kết luận

Mô hình BLoC có thể có vẻ phức tạp, nhưng việc phân chia nó thành các sự kiện, trạng thái và chính BLoC làm cho nó dễ quản lý hơn. Bằng cách tạo ra một luồng dữ liệu rõ ràng và đơn chiều, bạn có thể xây dựng các ứng dụng mạnh mẽ, có thể kiểm tra và dễ dàng mở rộng.

Điểm quan trọng là để các sự kiện điều khiển logic của bạn và các trạng thái điều khiển UI của bạn. Bằng cách hoàn toàn chấp nhận sự tách biệt này, bạn sẽ trên đường làm chủ quản lý trạng thái trong các dự án Flutter của mình và viết mã sạch hơn, dễ bảo trì hơn.

Các Thực Hành Tốt Nhất

  • Tách biệt logic kinh doanh: Đảm bảo rằng tất cả logic kinh doanh đều nằm trong BLoC, không trong các widget.
  • Sử dụng Equatable: Để đảm bảo rằng các trạng thái và sự kiện của bạn là độc nhất, sử dụng thư viện Equatable.

Những Cạm Bẫy Thường Gặp

  • Không xử lý lỗi: Đảm bảo rằng bạn xử lý các trường hợp lỗi trong BLoC để không làm treo ứng dụng.
  • Quá nhiều logic trong UI: Tránh đặt quá nhiều logic xử lý trong các widget; hãy giữ cho chúng đơn giản.

Mẹo Hiệu Suất

  • Sử dụng Stream: BLoC sử dụng Streams, vì vậy hãy chắc chắn rằng bạn quản lý các Stream cho hiệu suất tốt nhất.

Các Câu Hỏi Thường Gặp (FAQ)

1. BLoC có thể sử dụng cho các ứng dụng không Flutter không?
BLoC chủ yếu được phát triển cho Flutter, nhưng nguyên tắc của nó có thể áp dụng cho bất kỳ ứng dụng nào cần quản lý trạng thái.

2. Tại sao không sử dụng Provider thay vì BLoC?
Provider là một lựa chọn tuyệt vời cho việc quản lý trạng thái, nhưng BLoC cung cấp một mô hình rõ ràng hơn cho các ứng dụng phức tạp hơn.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào