Flutter bloc là gì

25/06/2022 admin

Học Bloc Pattern theo cách dễ hiểu nhất    

  • Báo cáo

Lời mở đầu

Sau khi mình đã san sẻ xong series về Stream, RxDart thì mình liên tục san sẻ về Bloc Pattern. Mình san sẻ theo lộ trình như vậy là vì để hiểu được Bloc Pattern thì bạn nên hiểu về Stream trước. Bạn hoàn toàn có thể tìm hiểu thêm series về Stream mình viết khá không thiếu :Nội dung chính

  • Học Bloc Pattern theo cách dễ hiểu nhất    
  • Lời mở đầu
  • 1. Bloc Pattern là gì
  • 2. Code minh họa
  • Bước 1: Định nghĩa các Event
  • Bước 2: Định nghĩa các State
  • Bước 3: Code business logic trong Bloc
  • Bước 4: apply bloc vào UI
  • Code chạy ra sao
  • 3. Build app flutter đơn giản từ code minh họa
  • Kết luận

Chinh phục Stream và RxDart trong 3 nốt nhạc

Ok, giờ chúng ta bắt đầu thôi.

Bạn đang đọc: Flutter bloc là gì

1. Bloc Pattern là gì

Tất nhiên nó là một Pattern rồi, mục tiêu của Pattern này là tách code business logic ra khỏi UI thay vì code gộp chung cả logic và UI vô cùng 1 file, để sau này spec mới có nhu yếu sửa code business logic hay sửa UI sẽ thuận tiện sửa hơn. Code business logic được tách ra đó người ta đặt tên là Bloc ( Business Logic Component ). Bên cạnh đó, nó còn giúp tất cả chúng ta quản trị state của 1 màn hình hiển thị tốt hơn vì những state sẽ được quản ở Bloc tách biệt với UI. Chính thế cho nên, mỗi màn hình hiển thị trong app flutter tất cả chúng ta nên tạo ra 1 bloc để giải quyết và xử lý logic của màn hình hiển thị đó và quản trị state của cả màn hình hiển thị đó .Lấy ví dụ trong dự án Bất Động Sản thực tiễn lun nha, khi user click vào button tải về tức là user gửi cái link URL của ảnh vào Bloc, Bloc sẽ nhận link URL đó và giải quyết và xử lý tải về thành cái ảnh và truyền cho UI để UI hiển thị lên màn hình hiển thị .

Tóm lại, quy mô của Bloc trông sẽ thế này. Truyền sự kiện vào bloc, bloc giải quyết và xử lý và cho ra output là State của UI .Ok, sáng tạo độc đáo của Bloc chỉ đơn thuần vậy thôi. Ý tưởng đó nó khá là tương thích với Stream Controller. Vì vậy để tiến hành sáng tạo độc đáo đó, người ta sử dụng đến StreamController

Input sẽ được thêm vào sink của StreamController và phía UI sẽ sử dụng stream để lắng nghe nhận state mỗi khi có event được add vào sink. (Nếu bạn chưa hiểu về cách hoạt động của StreamController, bạn có thể tìm đọc tại đây)

2. Code minh họa

Để minh họa cho bloc pattern, mình sẽ sử dụng code Dart chay trước để lượt bỏ bớt code UI rườm rà rồi tiếp đến mình sẽ thiết kế xây dựng app Flutter từ code minh họa này .Bài toán ở đây là giả lập cái remote tinh chỉnh và điều khiển TV với 3 button đơn thuần là : tăng âm lượng, giảm âm lượng và mute ( tắt tiếng )Để tiến hành bloc thường thì qua 4 bước được chia ra 4 file :

Bước 1: Định nghĩa các Event

Tạo 1 file là remote_event.dart và định nghĩa các event sau: event tăng âm lượng, giảm âm lượng và mute.

abstract class RemoteEvent {}

// event tăng âm lượng, user muốn tăng lên bao nhiêu thì truyền vào biến increment
class IncrementEvent extends RemoteEvent {
  IncrementEvent(this.increment);

  final int increment;
}

// event giảm âm lượng, user muốn giảm bao nhiêu thì truyền vào biến decrement
class DecrementEvent extends RemoteEvent {
  DecrementEvent(this.decrement);

  final int decrement;
}

// event mute
class MuteEvent extends RemoteEvent {}

Bước 2: Định nghĩa các State

Tạo 1 file là remote_state.dart, ở đây app mình chỉ quan tâm đến data duy nhất là (volume) âm lượng để update UI.

class RemoteState {
  RemoteState(this.volume);

  final int volume;
}

Bước 3: Code business logic trong Bloc

Tạo 1 file là remote_bloc.dart để code business logic. Như mình đã giới thiệu ở trên, nhiệm vụ của bloc là nhận event từ UI, phân biệt UI vừa gửi cho mình event gì (có thể là event tăng âm lượng hoặc mute, …) để xử lý chúng và truyền state lại cho UI.

import 'dart:async';

import 'remote_event.dart';
import 'remote_state.dart';

class RemoteBloc {
  var state = RemoteState(70); // init giá trị khởi tạo của RemoteState. Giả sử TV ban đầu có âm lượng 70

  // tạo 2 controller
  // 1 cái quản lý event, đảm nhận nhiệm vụ nhận event từ UI
  final eventController = StreamController();
  
  // 1 cái quản lý state, đảm nhận nhiệm vụ truyền state đến UI
  final stateController = StreamController();

  RemoteBloc() {
    // lắng nghe khi eventController push event mới  
    eventController.stream.listen((RemoteEvent event) {
      // người ta thường tách hàm này ra 1 hàm riêng và đặt tên là: mapEventToState
      // đúng như cái tên, hàm này nhận event xử lý và cho ra output là state
      
      if (event is IncrementEvent) {
        // nếu eventController vừa add vào 1 IncrementEvent thì chúng ta xử lý tăng âm lượng
        state = RemoteState(state.volume + event.increment);
      } else if (event is DecrementEvent) {
        // xử lý giảm âm lượng
        state = RemoteState(state.volume - event.decrement);
      } else {
        // xử lý mute
        state = RemoteState(0);
      }

      // add state mới vào stateController để bên UI nhận được
      stateController.sink.add(state);
    });
  }

  // khi không cần thiết thì close tất cả controller
  void dispose() {
    stateController.close();
    eventController.close();
  }
}

Bước 4: apply bloc vào UI

Cuối cùng sẽ code vào file có UI, giả sử là file main.dart

import 'remote_bloc.dart';
import 'remote_event.dart';
import 'remote_state.dart';

void main() async {
  // tạo đối tượng bloc
  final bloc = RemoteBloc();

  // UI lắng nghe state thay đổi để update UI
  bloc.stateController.stream.listen((RemoteState state) {
    print('Âm lượng hiện tại: ${state.volume}');
  });

  // giả sử 1s sau, user click vào tăng âm lượng thêm 5
  await Future.delayed(Duration(seconds: 1));
  bloc.eventController.sink.add(IncrementEvent(5)); // từ UI push event đến bloc

  // giả sử 2s sau, user click vào giảm âm lượng đi 10
  await Future.delayed(Duration(seconds: 2));
  bloc.eventController.sink.add(DecrementEvent(10)); // từ UI push event đến bloc

  // giả sử 3s sau, user click vào mute luôn
  await Future.delayed(Duration(seconds: 3));
  bloc.eventController.sink.add(MuteEvent()); // từ UI push event đến bloc
}

Run chương trình và tất cả chúng ta nhận được output là :

Âm lượng hiện tại: 75 // từ 70 tăng thêm 5
Âm lượng hiện tại: 65 // từ 75 giảm xuống 10
Âm lượng hiện tại: 0 // mute

Code chạy ra sao

Ở đây ta có 2 StreamController là eventControllerstateController. Khi user thực hiện thao tác gì đó (ví dụ click vào mute) thì chúng ta sẽ push event input đó vào eventController.sink và đăng ký lắng nghe event đó tại eventController.stream (ngay khi khởi tạo đối tượng RemoteBloc là mình đã cho lắng nghe nhận event ngay). Vậy là chúng ta đã nhận được event input từ user input, chúng ta sẽ xử lý event đó (nếu là tăng âm lượng thì xử lý tăng, giảm thì xử lý giảm và mute thì xử lý mute). Xử lý xong cho ra output là state chúng ta sẽ push event state đó vào stateController.sink và ở phía UI sẽ đăng ký nhận event từ stateController.streamđể update UI.

Event từ user input -> 
add vào eventController.sink -> 
eventController.stream nhận event đó -> 
code logic xử lý event đó cho ra output là state -> 
add state đó vào stateController.sink -> 
stateController.stream nhận state đó -> 
ở UI đăng ký lắng nghe stateController.stream và update UI

3. Build app flutter đơn giản từ code minh họa

Giờ chúng ta sẽ áp dụng code mô phỏng cái remote TV trên vào app thật. Có 1 widget hỗ trợ chúng ta làm update UI là StreamBuilder. 3 file remote_event.dart, remote_state.dart, remote_bloc.dart sẽ được tái sử dụng lại nhé. Chúng ta sẽ chỉ replace code trong main.dart

Vì nhìn code UI hơi rối rắm nên mình có đánh dấu code <=== new đó là những chỗ apply code minh họa ở phần 2 vào UI nha. Chỉ cần chú ý đến nó là đủ

import 'package:bloc_remote_demo/remote_bloc.dart';
import 'package:bloc_remote_demo/remote_event.dart';
import 'package:bloc_remote_demo/remote_state.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State {
  final bloc = RemoteBloc(); // khởi tạo bloc  <=== new

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: StreamBuilder( // sử dụng StreamBuilder để lắng nghe Stream <=== new
          stream: bloc.stateController.stream, // truyền stream của stateController vào để lắng nghe <=== new
          initialData: bloc.state, // giá trị khởi tạo chính là volume 70 hiện tại <=== new
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            return Text('Âm lượng hiện tại: ${snapshot.data.volume}'); // update UI <=== new
          },
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => bloc.eventController.sink.add(IncrementEvent(5)), // add event <=== new
            child: Icon(Icons.volume_up),
          ),
          FloatingActionButton(
            onPressed: () => bloc.eventController.sink.add(DecrementEvent(10)), // add event <=== new
            child: Icon(Icons.volume_down),
          ),
          FloatingActionButton(
            onPressed: () => bloc.eventController.sink.add(MuteEvent()), // add event <=== new
            child: Icon(Icons.volume_mute),
          )
        ],
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    bloc.dispose(); // dispose bloc <=== new
  }
}

Đây là thành quả:

Kết luận

Như vậy, mình đã giới thiệu về gốc gác của Bloc Pattern, bây giờ để có thể tăng tốc độ code các bạn có thể học cách sử dụng các package như flutter_bloc.

Nguồn tìm hiểu thêm : https://medium.com/flutterpub/bloc-state-management-with-easy-approach-b53fb6d15829

https://www.youtube.com/watch?v=oxeYeMHVLII&list=PLB6lc7nQ1n4jCBkrirvVGr5b8rC95VAQ5&index=1&ab_channel=ResoCoder  

Alternate Text Gọi ngay