0
0
Lập trình
TT

Quản lý trạng thái trong Flutter: Hướng dẫn Provider, Riverpod và BLoC

Đăng vào 2 tuần trước

• 9 phút đọc

Giới thiệu

Khi bắt đầu xây dựng ứng dụng Flutter, việc quản lý trạng thái có vẻ đơn giản. Nhưng khi ứng dụng của bạn phát triển, việc truyền dữ liệu giữa các màn hình có thể trở nên rối rắm. Bạn sẽ gặp phải mã nguồn khó đọc và khó gỡ lỗi. Đó chính là lúc các giải pháp quản lý trạng thái trở nên cần thiết.

Hãy tưởng tượng nó giống như việc tổ chức một nhà bếp chung. Ban đầu, với chỉ một người, bạn có thể để mọi thứ ở bất kỳ đâu. Nhưng khi ngày càng nhiều người bắt đầu sử dụng, bạn cần một hệ thống—nhãn, kệ, khu vực được chỉ định—để tránh tình trạng hỗn loạn.

Trong bài viết này, chúng ta sẽ xem xét ba hệ thống quản lý trạng thái phổ biến trong Flutter: Provider, Riverpod, và BLoC. Chúng ta sẽ sử dụng các ví dụ đơn giản để hiểu cách hoạt động của chúng và giúp bạn quyết định giải pháp nào phù hợp nhất cho dự án của mình.

Trạng Thái Là Gì?

Nói một cách đơn giản, trạng thái chỉ là dữ liệu có thể thay đổi theo thời gian. Đó có thể là giá trị của một bộ đếm, trạng thái đăng nhập của người dùng, hoặc danh sách các mặt hàng trong giỏ hàng. Khi dữ liệu này thay đổi, giao diện người dùng (UI) cần được cập nhật để phản ánh sự thay đổi đó.

Nếu không có một hệ thống quản lý thích hợp, bạn có thể thấy mình phải truyền dữ liệu qua nhiều lớp widget mà thậm chí không cần thiết, một vấn đề thường được gọi là "prop drilling". Một giải pháp quản lý trạng thái tốt sẽ cung cấp cho bạn một cách sạch sẽ để truy cập và sửa đổi dữ liệu từ bất kỳ đâu trong ứng dụng của bạn.

Provider: Bước Khởi Đầu Đơn Giản

Provider thường là công cụ quản lý trạng thái đầu tiên mà các nhà phát triển Flutter học. Nó được xây dựng dựa trên InheritedWidget của Flutter nhưng dễ sử dụng hơn rất nhiều.

Hãy tưởng tượng Provider như một bảng thông báo trung tâm trong một văn phòng. Thay vì phải hỏi từng đồng nghiệp để lấy một thông tin, bạn chỉ cần nhìn vào bảng thông báo. Bất kỳ widget nào cần truy cập vào trạng thái đều có thể lấy trực tiếp, miễn là nó là con của nơi mà trạng thái được "cung cấp".

Cách Thức Hoạt Động Của Provider

Bạn tạo một lớp mô hình chứa trạng thái của bạn, thường sử dụng ChangeNotifier. Sau đó, bạn sử dụng ChangeNotifierProvider để làm cho mô hình đó khả dụng cho các widget bên dưới nó.

Ví dụ Đơn Giản Về Bộ Đếm

1. Tạo Lớp Mô Hình Bộ Đếm

dart Copy
import 'package:flutter/material.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Thông báo cho widget để xây dựng lại
  }
}

2. Cung Cấp Mô Hình
Bọc một widget ở vị trí cao trong cây (như MaterialApp) với ChangeNotifierProvider.

dart Copy
ChangeNotifierProvider(
  create: (context) => CounterModel(),
  child: MyApp(),
),

3. Sử Dụng Trạng Thái
Bây giờ, bất kỳ widget nào trong MyApp đều có thể truy cập vào CounterModel.

dart Copy
// Để hiển thị giá trị bộ đếm
Text('Count: ${context.watch<CounterModel>().count}'),

// Để gọi hàm tăng
ElevatedButton(
  onPressed: () => context.read<CounterModel>().increment(),
  child: Text('Tăng'),
)

Khi Nào Sử Dụng Provider: Nó tuyệt vời cho các ứng dụng nhỏ đến vừa hoặc khi bạn mới bắt đầu. Nó đơn giản để hiểu và hoàn thành công việc.

Riverpod: Nâng Cấp Hiện Đại

Riverpod được tạo ra bởi cùng một tác giả với Provider để khắc phục một số vấn đề phổ biến của nó. Sự khác biệt lớn nhất là Riverpod an toàn biên dịchđộc lập với cây widget.

Điều này có nghĩa là bạn nhận được lỗi tại thời điểm biên dịch thay vì thời điểm chạy, và bạn không cần BuildContext để truy cập vào trạng thái của mình. Nó giống như việc nhận thông tin được giao trực tiếp đến bạn qua một người đưa tin thay vì phải đi đến bảng thông báo trung tâm.

Ví Dụ Đơn Giản Về Riverpod

Với Riverpod, bạn định nghĩa các "provider" toàn cục chứa một phần trạng thái.

1. Định Nghĩa Một Provider
Chúng ta sẽ sử dụng một StateNotifierProvider, rất phù hợp để quản lý trạng thái có thể thay đổi.

dart Copy
// 1. Tạo lớp Notifier
class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0); // Trạng thái ban đầu là 0
  void increment() => state++;
}

// 2. Tạo provider (một biến toàn cục)
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

2. Thiết Lập Ứng Dụng Của Bạn
Bọc toàn bộ ứng dụng của bạn trong một ProviderScope.

dart Copy
void main() {
  runApp(ProviderScope(child: MyApp()));
}

3. Sử Dụng Trạng Thái Trong Một Widget
Để sử dụng provider, bạn thay đổi StatelessWidget của mình thành ConsumerWidget.

dart Copy
class CounterText extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Xây dựng lại widget khi bộ đếm thay đổi
    final count = ref.watch(counterProvider);
    return Text('Count: $count');
  }
}

// Trong một widget khác, cho nút
class IncrementButton extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        // Lấy notifier và gọi phương thức tăng
        ref.read(counterProvider.notifier).increment();
      },
      child: Text('Tăng'),
    );
  }
}

Khi Nào Sử Dụng Riverpod: Đây là lựa chọn tuyệt vời cho các dự án mới ở bất kỳ quy mô nào. Nó mở rộng tốt và ít lỗi hơn so với Provider.

BLoC: Cỗ Máy Dự Đoán Mạnh Mẽ

BLoC (Business Logic Component) là một mẫu thiết kế tách biệt logic kinh doanh của ứng dụng khỏi UI. Nó có cấu trúc hơn và phức tạp hơn một chút so với Provider hoặc Riverpod.

Hãy tưởng tượng BLoC như một dây chuyền lắp ráp nghiêm ngặt. Một yêu cầu (một Sự Kiện) đi vào một đầu. Nó được xử lý theo một tập hợp quy tắc. Một kết quả (một Trạng Thái) đi ra đầu kia. Dòng chảy này là đơn hướng và rất dễ đoán, điều này làm cho nó tuyệt vời cho việc kiểm thử và quản lý logic phức tạp.

Dòng Chảy Của BLoC

  1. UI gửi một Sự Kiện: Một lần nhấn nút, nhập văn bản, v.v.
  2. BLoC nhận Sự Kiện: Nó xử lý sự kiện và logic của nó.
  3. BLoC phát ra một Trạng Thái mới: BLoC cập nhật trạng thái của nó.
  4. UI xây dựng lại: UI nghe các thay đổi trạng thái và xây dựng lại bản thân tương ứng.

Ví Dụ Bộ Đếm BLoC

Sử dụng gói flutter_bloc để đơn giản hóa mẫu này.

1. Định Nghĩa Các Sự Kiện và Trạng Thái

dart Copy
// Các Sự Kiện
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}

// Các Trạng Thái (chỉ là một int cho trường hợp đơn giản này)
// Đối với các tính năng phức tạp hơn, điều này sẽ là một lớp.

2. Tạo BLoC

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

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) { // Trạng thái ban đầu là 0
    on<IncrementEvent>((event, emit) {
      emit(state + 1);
    });
  }
}

3. Cung Cấp và Sử Dụng BLoC

dart Copy
// Cung cấp BLoC ở trên cùng của cây
BlocProvider(
  create: (context) => CounterBloc(),
  child: CounterPage(),
),

// Sử dụng BlocBuilder để lắng nghe các thay đổi trạng thái và xây dựng lại UI
BlocBuilder<CounterBloc, int>(
  builder: (context, count) {
    return Text('Count: $count');
  },
),

// Thêm một sự kiện vào BLoC
ElevatedButton(
  onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
  child: Text('Tăng'),
)

Khi Nào Sử Dụng BLoC: BLoC nổi bật trong các ứng dụng lớn, phức tạp nơi logic trạng thái phức tạp và khả năng kiểm thử là ưu tiên hàng đầu. Nó có một đường cong học tập dốc hơn nhưng cung cấp một cấu trúc rất mạnh mẽ.

Vậy, Bạn Nên Chọn Giải Pháp Nào?

Không có một giải pháp "tốt nhất" duy nhất. Nó phụ thuộc vào dự án của bạn.

  • Provider: Sử dụng cho các ứng dụng nhỏ hơn hoặc nếu bạn là người mới bắt đầu. Nó đơn giản và hiệu quả.
  • Riverpod: Một lựa chọn tuyệt vời cho hầu hết các dự án mới. Nó mạnh mẽ, linh hoạt và khắc phục những nhược điểm chính của Provider.
  • BLoC: Chọn nó cho các ứng dụng quy mô lớn nơi bạn cần kiến trúc nghiêm ngặt và sự thay đổi trạng thái dự đoán được.

Điều quan trọng nhất là chọn một giải pháp và sử dụng nó một cách nhất quán. Bắt đầu đơn giản và đừng ngại thử một cách tiếp cận khác khi ứng dụng của bạn và kỹ năng của bạn phát triển.

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

  • Hiểu rõ cách mỗi giải pháp hoạt động: Trước khi bắt đầu, hãy đảm bảo bạn hiểu cách mỗi giải pháp quản lý trạng thái hoạt động và khi nào nên sử dụng nó.
  • Giữ mã nguồn sạch sẽ: Sử dụng các mô hình và cấu trúc rõ ràng để duy trì sự dễ hiểu cho mã nguồn của bạn.
  • Tối ưu hóa hiệu suất: Theo dõi hiệu suất của ứng dụng khi sử dụng các giải pháp quản lý trạng thái để đảm bảo không có vấn đề về hiệu suất.

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

  • Quá phức tạp hóa: Đừng biến những ứng dụng nhỏ thành những dự án phức tạp với BLoC nếu không cần thiết. Hãy giữ mọi thứ đơn giản.
  • Không theo dõi trạng thái: Đảm bảo rằng bạn không quên theo dõi các thay đổi trạng thái để tránh lỗi.

Mẹo Tối Ưu Hiệu Suất

  • Sử dụng bộ nhớ cache: Giảm thiểu số lần xây dựng lại widget bằng cách sử dụng bộ nhớ cache cho dữ liệu đã tải.
  • Tối ưu hóa cấu trúc cây widget: Tránh việc xây dựng lại toàn bộ cây widget khi chỉ cần xây dựng lại một phần nhỏ.

Khắc Phục Vấn Đề

  • Kiểm tra lỗi: Nếu gặp lỗi, hãy kiểm tra các lớp mô hình và provider của bạn để đảm bảo rằng chúng hoạt động đúng cách.
  • Sử dụng công cụ gỡ lỗi: Sử dụng các công cụ gỡ lỗi để theo dõi trạng thái và sự kiện để phát hiện vấn đề sớm hơn.

FAQ

  1. Khi nào nên sử dụng Provider?
    • Provider là lựa chọn tốt cho các ứng dụng nhỏ đến vừa, dễ sử dụng và hiểu.
  2. Riverpod có gì khác biệt so với Provider?
    • Riverpod an toàn hơn khi biên dịch và không phụ thuộc vào cây widget, giúp dễ dàng hơn trong việc quản lý trạng thái.
  3. Khi nào nên chọn BLoC?
    • BLoC là lựa chọn tốt cho các ứng dụng lớn với logic phức tạp và yêu cầu kiểm thử cao.

Kết Luận

Trong bài viết này, chúng ta đã khám phá ba giải pháp quản lý trạng thái phổ biến trong Flutter: Provider, Riverpod và BLoC. Mỗi giải pháp có ưu và nhược điểm riêng, vì vậy hãy chọn giải pháp phù hợp với dự án và kỹ năng của bạn. Đừng ngần ngại thử nghiệm và tìm ra cách tốt nhất cho ứng dụng của bạn. Hãy bắt đầu ngay hôm nay và phát triển kỹ năng lập trình Flutter của bạ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