, October 26, 2021

0 kết quả được tìm thấy

RxDart và cách sử dụng với BLoC Pattern trong Flutter?

  • Đăng bởi  Chau Le
  •  Aug 03, 2021

  •   9 min reads
RxDart và cách sử dụng với BLoC Pattern trong Flutter?

RxDart là một thư viện reactive functional programming cho ngôn ngữ Dart, dựa trên ReactiveX. Dart đã có một package phù hợp để làm việc với Streams, nhưng RxDart bổ sung thêm một số tính năng để sử dụng tiện lợi hơn.

Chúng ta hãy bắt đầu tìm hiểu về Streams.

Streams and Sinks

Streams đại diện cho luồng dữ liệu (stream) và sự kiện (event), và tại sao nó quan trọng? Với Streams, bạn có thể nghe các thay đổi dữ liệu và sự kiện, đồng thời giải quyết những gì đến từ Streams với listener. Làm thế nào nó có thể được áp dụng cho Flutter?

Ví dụ: chúng ta có một Widget trong Flutter có tên là StreamBuilder sẽ build dựa trên snapshot mới nhất dựa trên tương tác với Stream và khi có dữ liệu mới, Widget sẽ tải lại để xử lý. Widget Weekly of Flutter Dev Channel cung cấp nội dung tuyệt vời về cách hoạt động của StreamBuilder.

Còn về Sinks? Nếu chúng ta có một output của stream, thì chúng ta cần một nơi nào đó để input, đó là những gì Sinks được sử dụng, có vẻ đơn giản phải không? Bây giờ chúng ta hãy xem về BLoC pattern và cách để chúng ta có thể kết hợp cả hai khái niệm này thành một ứng dụng Flutter tuyệt vời.

BLoC Pattern

BLoC Pattern (Bussiness Logic Component) đã được Paolo Soares công bố chính thức trong hội nghị Dart 2018. Nếu bạn đã xem video, có lẽ bạn đã nhận ra rằng đề xuất ban đầu là sử dụng lại code liên quan đến business logic trong các nền tảng khác, trong trường hợp này là Angular Dart. Ngay lập tức, những gì mà pattern này làm là lấy tất cả business logic code ra khỏi giao diện người dùng và chỉ sử dụng nó trong các lớp BLoC class. Nó giúp cho code có tính độc lập của môi trường và nền tảng, bên cạnh việc đặt các responsibility vào đúng component. Và bây giờ mọi thứ sẽ rõ ràng hơn nhiều, bởi vì BLoC Pattern chỉ dựa vào việc sử dụng Stream.

BloC pattern

Nhìn vào hình trên, chúng ta có thể nhận ra kiến trúc flux. Các Widget gửi dữ liệu/sự kiện đến BLoC class thông qua Sink và được Stream thông báo. Nhận thấy rằng không có business logic nào trong widget, điều đó có nghĩa là những gì đã xảy ra trong BLoC không phải là mối quan tâm của giao diện người dùng. Kiến trúc này cải thiện để thực hiện Unit Test dễ dàng hơn, trong đó các trường hợp kiểm tra business logic chỉ cần áp dụng cho các BLoC class.

Cái nhìn về RxDart

RxDart hiện tại (tại thời điểm của bài đăng này) là phiên bản 0.21.0. Và ở đây mình sẽ nói về một số object mà thư viện mang đến cho chúng ta.

Observable<T> class

Observable cho phép chúng ta gửi thông báo đến các Widget đang quan sát nó và sau đó xử lý luồng dữ liệu. Observable class trong RxDart mở rộng từ Stream, ngụ ý :

  • Tất cả các method được định nghĩa trên Stream class cũng tồn tại trên Observable.
  • Tất cả các Observable có thể được chuyển tới bất kỳ API nào yêu cầu Dart Stream làm input (bao gồm cả StreamBuilder Widget chẳng hạn).

PublishSubject<T> class

Điều này là khá đơn giản. Subject cho phép gửi dữ liệu, lỗi và các sự kiện đã thực hiện đến listener. Ở đây nó sẽ hoạt động với Sinks, mà chúng ta đã nói trước đây. Xem ví dụ trên:

PublishSubject<int> subject = new PublishSubject<int>();

/*this listener below will print every integer added to the subject: 1, 2, 3, ...*/
subject.stream.listen(print);
subject.add(1);
subject.add(2);

/*but this listener below will print only the integer added after his initialization: 3, .../*
subject.stream.listen(print);
subject.add(3);

BehaviorSubject<T> class

Cái này tương tự như PublishSubject. Nó cũng cho phép gửi dữ liệu, lỗi và các sự kiện đã thực hiện đến listener, nhưng giá trị sau cùng đã được thêm vào subject sẽ được gửi đến các listener được thêm vào sau này. Nhưng sau đó, mọi sự kiện mới sẽ được gửi đến listener một cách bình thường. Xem ví dụ:

BehaviorSubject<int> subject = new BehaviorSubject<int>();
subject.stream.listen(print); // prints 1,2,3 
subject.add(1);
subject.add(2);
subject.add(3);

subject.stream.listen(print); // prints 3

ReplaySubject<T> class

ReplaySubject cho phép chúng ta: gửi dữ liệu, lỗi và các sự kiện đã thực hiện đến listener. Nhưng với một sự khác biệt quan trọng ở đây. Khi các giá trị được thêm vào subject, ReplaySubject sẽ lưu trữ chúng và khi stream có listener, tất cả giá trị đã ghi đó sẽ được gởi tới listener. Xem ví dụ:

ReplaySubject<int> subject = new ReplaySubject<int>();

subject.add(1);
subject.add(2);
subject.add(3);

subject.stream.listen(print); // prints 1, 2, 3

RxDart trong thực tế

Trong bài viết này, mình sẽ chỉ cho các bạn một ví dụ đơn giản về việc sử dụng RxDart và các nguyên tắc của BLoC pattern.

Một cách tuyệt vời để bắt đầu nó là ngay từ đầu với ứng dụng Flutter Hello World. Có thể bạn đã quen với increment function (chức năng tăng dần) trên ứng dụng demo này, nhưng để hiểu rõ hơn, chúng ta hãy tạo cả decrement function (chức năng giảm dần). Vì vậy, trước hết, hãy tạo một dự án flutter và nhập rxdart vào dự án của bạn.

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter() {
    setState(() {
      _counter--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text('You have pushed the button this many times:',),
            new Text('$_counter', style: Theme.of(context).textTheme.display1),
          ],
        ),
      ),
      floatingActionButton: new Column(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
        new Padding(padding: EdgeInsets.only(bottom: 10), child:
          new FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: new Icon(Icons.add),
          )
        ),
        new FloatingActionButton(
          onPressed: _decrementCounter,
          tooltip: 'Decrement',
          child: new Icon(Icons.remove),
        ),
      ])
    );
  }
}

Như bạn có thể thấy, code này triển khai chức năng tăng và giảm, nhưng vẫn không áp dụng BLoC pattern hoặc thậm chí là Streams. Code này hoạt động và nó khá đơn giản, nhưng nếu bạn chú ý, bạn sẽ thấy rằng chúng ta có hai logic business function trong code giao diện người dùng: tăng và giảm. Vì vậy, hãy tưởng tượng nếu ứng dụng này là một ứng dụng lớn mà bạn đang làm việc chăm chỉ, nhưng bây giờ yêu cầu đã được thay đổi và số gia tăng cần phải thêm hai cùng một lúc. Bạn có đồng ý với mình không (trong trường hợp này) một yêu cầu thay đổi trong logic nghiệp vụ sẽ không ảnh hưởng đến code giao diện người dùng, phải không? Nếu có, tuyệt vời! Bạn hiểu rồi, đó là điểm để phân biệt các responsibility.

Bây giờ, hãy tách nó ra và sử dụng những gì chúng ta đã học được cho đến nay. Hãy tạo CounterBLoC class của chúng ta:

import 'package:rxdart/rxdart.dart';

class CounterBloc {

  int initialCount = 0; //if the data is not passed by paramether it initializes with 0
  BehaviorSubject<int> _subjectCounter;

  CounterBloc({this.initialCount}){
   _subjectCounter = new BehaviorSubject<int>.seeded(this.initialCount); //initializes the subject with element already
  }

  Observable<int> get counterObservable => _subjectCounter.stream; 

  void increment(){
    initialCount++;
    _subjectCounter.sink.add(initialCount);
  }

  void decrement(){
    initialCount--;
    _subjectCounter.sink.add(initialCount);
  }

  void dispose(){
    _subjectCounter.close();
  }
  
}

Tuyệt vời! Bây giờ hãy để mình giải thích đoạn code trên. Chúng ta đã tạo một class có tên CounterBloc có import thư viện rxdart. Trong trường hợp này, chúng ta cần nhận được initialCount. Nó cho phép chúng ta biết counter của chúng ta sẽ bắt đầu từ số nào. Mình chọn cho ví dụ này là BehaviorSubeject, và sau đó mình khởi tạo Subject với dữ liệu được truyền bằng tham số, nói cách khác, khi Widget trở thành một listener của Subject, giá trị đầu tiên được chuyển qua stream sẽ là initialCount được đặt trong CounterBloc constructor. Bây giờ hãy nói về các method. Trong trường hợp này, chúng ta có bốn method trong class:

  • increment(): tăng initialCount và gửi tới các Subject listener bằng cách Sink giá trị mới.
  • decment(): giảm initialCount và gửi cho Subject listener bằng cách Sink giá trị mới.
  • dispose(): đóng subject đã mở.
  • counterObeservable(): trả về một Observable, nói cách khác, object sẽ được sử dụng để thông báo cho các Widget khi các thay đổi xảy ra trong Stream.

Bây giờ chúng ta đã tạo BLoC class, hãy xem việc tích hợp nó với giao diện người dùng.

import 'package:flutter/material.dart';
import 'package:bloc_example/bloc/CounterBloc.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  CounterBloc _counterBloc = new CounterBloc(initialCount: 0);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text('You have pushed the button this many times:'),
            new StreamBuilder(stream: _counterBloc.counterObservable, builder: (context, AsyncSnapshot<int> snapshot){
              return new Text('${snapshot.data}', style: Theme.of(context).textTheme.display1);
            })
          ],
        ),
      ),
      floatingActionButton: new Column(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
        new Padding(padding: EdgeInsets.only(bottom: 10), child:
          new FloatingActionButton(
            onPressed: _counterBloc.increment,
            tooltip: 'Increment',
            child: new Icon(Icons.add),
          )
        ),
        new FloatingActionButton(
          onPressed: _counterBloc.decrement,
          tooltip: 'Decrement',
          child: new Icon(Icons.remove),
        ),
      ])
    );
  }

  @override
  void dispose() {
    _counterBloc.dispose();
    super.dispose();
  }
  
}

Chúng ta đã thay đổi một số thứ trong giao diện người dùng:

  • Bây giờ chúng ta khởi tạo CounterBloc với InitialCount = 0.
  • Sau đó, chúng ta loại bỏ các increment method và decrement method. Việc thực hiện các phương pháp đó không còn là trách nhiệm của UI nữa.
  • Khi FloatingActionButton được nhấp vào, nó sẽ call method tương ứng trong CounterBloc.
  • Bây giờ chúng ta sử dụng StreamBuilder để hiển thị dữ liệu của chúng ta trên màn hình. Chúng ta gọi chuyền StreamBuilder dưới dạng Stream counterObservable method của chúng ta có sẵn bởi CounterBloc class và chúng ta call builder phải xử lý dữ liệu đến từ Stream và trả về Widget thích hợp.

Tại thời điểm này, ứng dụng có cấu trúc tốt của chúng ta sẽ trông như thế này:

Bài viết được lược dịch từ Wilton Ribeiro.

Bài viết liên quan

[FREE EBOOK] TIPS & TRICKS GIÚP X3 TỐC ĐỘ CODE FLUTTER CỦA BẠN

Cuốn ebook "TIPS & TRICKS GIÚP X3 TỐC ĐỘ CODE FLUTTER CỦA BẠN" chỉ ra 32 trường hợp giúp bạn code nhanh hơn so với việc code thông thường...

[FREE EBOOK] TIPS & TRICKS GIÚP X3 TỐC ĐỘ CODE FLUTTER CỦA BẠN
Flutter 2.5 có gì mới?

Bản phát hành Flutter 2.5 lần này có rất nhiều bản cập nhật mới thú vị. Cùng bắt đầu tìm hiểu trong bài viết này nhé!...

Flutter 2.5 có gì mới?
Tìm hiểu Flutter Opacity Widget qua ví dụ cơ bản

Bài viết sau sẽ giới thiệu một cách tổng quan về Opacity widget và cung cấp đoạn code để bạn có thể tự thực hành thông qua những ví dụ cơ bản....

Tìm hiểu Flutter Opacity Widget qua ví dụ cơ bản
Flutter Tutorial 2021 #34 - Hướng dẫn tạo hiệu ứng "fancy" khi chuyển màn hình

Thêm một hiệu ứng mới nữa mà các bạn sẽ được giới thiệu tới! Đó chính là hiệu ứng "fancy" khi chuyển màn hình. Hiệu ứng này sẽ giúp tăng trải nghiệm và gây ấn tượng cực mạnh với người dùng....

Flutter Tutorial 2021 #34 - Hướng dẫn tạo hiệu ứng "fancy" khi chuyển màn hình
Flutter Tutorial 2021 #33 - "Giao tiếp" giữa 2 màn hình bất kỳ trong Flutter

Làm sao để giao tiếp 2 hoặc 3, 4 màn hình trong app với nhau? Chúng ta cùng xem video bên dưới để hiểu được cách truyền dữ liệu từ màn hình A sang màn hình B và ngược lại nhé!...

Flutter Tutorial 2021 #33 - "Giao tiếp" giữa 2 màn hình bất kỳ trong Flutter
You've successfully subscribed to 200Lab Blog
Great! Next, complete checkout for full access to 200Lab Blog
Xin chào mừng bạn đã quay trở lại
OK! Tài khoản của bạn đã kích hoạt thành công.
Success! Your billing info is updated.
Billing info update failed.
Your link has expired.