Quản lý state bằng Stream

Tram Ho

Giới thiệu Stream

Lập trình bất đồng bộ là một thuật ngữ phổ biến trong lập trình. Với ngôn ngữ lập trình Dart, chúng ta đã quen với Future class cung cấp một tính toán không được hoàn thành ngay lập tức và sẽ thông báo kết quả khi sẵn sàng, việc này chỉ thực hiện đúng 1 lần. Thay vào đó Stream là một luồng các sự kiện bất đồng bộ và cho phép chúng ta lắng nghe các sự kiện này khi chúng được bắn ra từ luồng.

A stream is a sequence of asynchronous events.

Phân loại Stream

  • Có 2 loại Stream
    • Single subscription streams
    • Broadcast streams

    Hiểu cơ bản là Single subscription streams chỉ có thể lắng nghe 1 lần duy nhất. (Nếu có sự kiện lắng nghe từ một nơi nào khác hoặc lần thứ 2 trở lên thì sẽ báo lỗi). Trái lại Broadcast streams có thể lắng nghe bất kỳ đâu.

Các phương thức

  • Sức mạnh của Stream không chỉ giúp việc lập trình bất đồng bộ dễ dàng mà nó còn có các bộ method hổ trợ rất mạnh mẽ như filter, transform…
  • Xêm thêm tại https://dart.dev/tutorials/language/streams
  • Thư viện support: extension cho stream Rxdart
  • Tham khảo https://reactivex.io/ để tìm hiểu về Reactivex/Functional reactive programming để tìm hiểu về cơ chế, cách hoạt động, cách sử dụng (support rất nhiều ngôn ngữ)

Có một câu hỏi nhỏ: Vậy điểm khác biệt giữa 2 loại Stream này là gì?
Tại sao nên sử dụng Stream

  • Stream là một thư viện/api core của Dart.
    • → Dễ dàng tạo ra các Plugin (Tách nhỏ repo)
    • → Nắm vững công nghệ
  • Stream phù hợp với phong cách *làm mới giao diện (declarative) của Flutter. (***so với cách Imperative ở Native) và mô hình MVVM.
  • Giảm phụ thuộc vào các thư viện của các bên thứ 3.
    • → Tránh lỗi khi thay đổi version
    • → Giảm app size
  • Một số thư viện như bloc, getx có cơ chế phụ thuộc vào Stream.

Xây dựng giao diện bằng StreamBuilder

StreamBuilder lắng nghe sự thay đổi của Stream và làm mới lại giao diện.

Để sử dụng StreamBuilder cần gọi

Ý nghĩa Parameters:

T? initialData: giá trị mặc định, nếu không truyền vào thì coi như chưa nhận được dữ liệu từ Stream
Stream<T>? stream: truyền Stream cần lắng nghe và được xử lý ở hàm builder
required AsyncWidgetBuilder<T> builder: Xây dựng giao diện được thiết lập ở đây

Khi xây dựng UI cần chú ý tới thành phần snapshot trong builder

  • Kiểm tra ConnectionState xem tình trạng kết nối với Stream
  • snapshot.hasError và snapshot.error: Kiểm tra có lỗi và lấy lỗi. (Xử dụng addError để bắn ra sự kiện lỗi)
  • snapshot.hasData và snapshot.data: Kiểm tra có dữ liệu và lấy dữ liệu. (Xử dụng add để bắn ra dữ liệu)

Các trạng thái của ConnectionState

  • Khi không truyền Stream vào StreamBuilder (có nghĩa Stream là null) → none
  • Khi truyền Stream (initialData có thể bằng null hoặc không) và chưa add sự kiện vào → waiting
  • Khi truyền Stream và add sự kiện → active
  • Khi truyền Stream và close() → done

Ví dụ về xử lý giao diện:

[Demo] Xây dựng ứng dụng đếm ngược thời gian bằng Stream

Demo bên dưới không xử lý các trạng thái lỗi
Các tính năng cơ bản:

Bắt đầu – Tạm dừng – Tiếp tục – Làm mới

UI Bắt đầu

UI khi đã Bắt đầu

Các bước thực hiện

Các khai báo và các hàm được viết trong State của StatefulWidget

  • Tạo StreamController để quản lý luồng dữ liệu

  • Tạo StreamSubscription để quản lý việc đếm ngược

Stream.periodic(**const** Duration(seconds: 1), (computationCount) => **_start** - computationCount) tạo một Stream trả về giá trị định kỳ sau 1 giây.

**_timeStreamController**.add(event); add thời gian mới vào Stream

  • Hiển thị dữ liệu lên giao diện

  • Extension lấy thông tin thời gian từ kiểu int

  • Nhằm giúp cho các công việc không thực hiện lại công việc nó đang thực hiện thực hiện gọi các function thông qua streamController

Full code →

[Demo] Tự tạo flutter_bloc theo style Cubit bằng Stream

  • Nhằm tăng tính đọc hiểu dữ liệu và phân chia mã nguồn giữa giao diện và logic chúng ta cần tách biệt xử lý ở các mà

Bước 1: Tạo abstract class để định nghĩa các biến và hàm cơ bản của bloc

**_streamCtrl** = StreamController.broadcast()..add(initValue);
stream không thể nhận được event này bới stream.listen được gọi sau khi hàm hàm constructor chạy.

Có thể tự initValue ở StreamBuilder hoặc dùng thư viện RxDart có hổ trợ chức năng này

 

Bước 2:

  • Tạo TimerCubit
  • Mang hết khai báo và hàm từ vào trong TimerCubit

Bước 3: Tạo BlocProvider

  • Việc tạo BlocProvider để quản lý instance của bloc bằng context nếu không muốn làm theo cách này có thể tạo biến toàn cục để quán lý riêng.
  • BlocProvider được ứng dùng từ InheritedWidget

Bước 4: Khai báo

Full code →

Các tính năng có thể bổ xung

  • Có thể custom _Content thành BlocBuilder
  • Thêm các tính năng như buildWhen, listenner
  • MultiBlocProvider

Tham khảo

https://dart.dev/tutorials/language/streams

https://medium.flutterdevs.com/exploring-streambuilder-in-flutter-5958381bca67

https://reactivex.io/

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo