Làm quen với flutter_bloc

Giới thiệu

flutter_bloc là 1 package trong flutter, dùng để quản lý state.

Cài đặt:

  • Flutter: flutter pub add flutter_bloc
  • Dart: dart pub add bloc

Import:

  • Flutter: import 'package:flutter_bloc/flutter_bloc.dart';
  • Dart: import 'package:bloc/bloc.dart';

Vì sao nên sử dụng Bloc Flutter?

Bloc giúp tách biệt phần business logic, giúp code nhanh hơn, dễ dàng kiểm thử và tái sử dụng. Bloc được thiết kế để:

  • Đơn giản: dễ hiểu, có thể sử dụng cho các lập trình viên có level khác nhau
  • Mạnh mẽ: giúp tạo các ứng dụng phức tạp bằng cách kết hợp với các thành phần nhỏ hơn
  • Có thể kiểm thử: dễ dàng kiểm thử

Các khái niệm trong bloc flutter

Streams

Stream là một luồng dữ liệu bất đồng bộ. Mời bạn xem lại bài viết Stream trong Flutter để hiểu rõ hơn về Stream

Cubit

Cubit là 1 lớp được extends BlocBase, có thể quản lý các state. Trong một Cubit, chúng ta có thể thêm các hàm để thay đổi state. UI có thể lắng nghe sự thay đổi state này để xử lý giao diện. Hiểu một cách đơn giản, UI gọi function của cubit để xử lý logic, sau đó lắng nghe state để vẽ lại giao diện.

Khởi tạo

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);
}
class CounterCubit extends Cubit<int> {
  CounterCubit(int initialState) : super(initialState);
}

Trong cubit, chúng ta cần có giá trị ban đầu cho state bằng việc khởi tạo giá trị thông qua constructor. Có 2 cách khởi tạo đó là tạo giá trị mặc định hoặc tạo giá trị qua params của constructor.

Thay đổi state

Để thay đổi state, chúng ta cần thêm các function bên trong, sử dụng emit để phát dữ liệu mới. Chúng ta có thể truy cập state hiện tại thông qua getter state của Cubit

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
}

Sử dụng

void main() {
  final cubit = CounterCubit();
  print(cubit.state); // 0
  cubit.increment();
  print(cubit.state); // 1
  cubit.close();
}

Để lắng nghe luồng thay đổi state của Cubit, chúng ta chỉ cần listen stream

Future<void> main() async {
  final cubit = CounterCubit();
  final subscription = cubit.stream.listen(print); // 1
  cubit.increment();
  await Future.delayed(Duration.zero);
  await subscription.cancel();
  await cubit.close();
}

Chú ý:

  • Những thay đổi state chỉ được nhận sau khi gọi listen trong Cubit. Ví dụ, khi cubit.increment() được gọi trước khi khởi tạo subscription, state vẫn sẽ thay đổi nhưng không có được print ra. Các state mới cũng không được nhận sau khi cancel subscription.
  • Khi Cubit được đóng (cubit.close()), các emit sau đó sẽ trở thành lỗi.
    Unhandled exception:
    Bad state: Cannot emit new states after calling close

Ví dụ

import 'package:bloc/bloc.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0); // Giá trị khởi tạo là 0

  void increment() => emit(state + 1); // Tăng số đếm
  void decrement() => emit(state - 1); // Giảm số đếm
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (context) => CounterCubit(),
        child: CounterScreen(),
      ),
    );
  }
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Cubit Counter')),
      body: Center(
        child: BlocBuilder<CounterCubit, int>(
          builder: (context, count) {
            return Text(
              '$count',
              style: TextStyle(fontSize: 40),
            );
          },
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => context.read<CounterCubit>().decrement(),
            tooltip: 'Decrease',
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: () => context.read<CounterCubit>().increment(),
            tooltip: 'Increase',
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

Bloc

Mở rộng hơn so với Cubit, Bloc định nghĩa các Event để biến đổi State thay vì function. Bloc sẽ nhận events từ UI, xử lý event để biến đổi states và thông báo với UI.

Tạo Bloc

sealed class CounterEvent {}

final class CounterIncrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);
}

Bloc sẽ định nghĩa ra Event, State để quản lý chúng. Giống như Cubit, state sẽ được khởi tạo giá trị mặc định trong constructor hoặc truyền thông qua params của constructor.

Thay đổi State

sealed class CounterEvent {}

final class CounterIncrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterIncrementPressed>((event, emit) {
      emit(state + 1);
    });
  }
}

Bloc yêu cầu chúng ta đăng ký xử lý event thông qua API on<Event>. Trong EventHandler, chúng ta nhận event và xử lý event đó, update lại state và phát đi thông qua emit.

Sử dụng

Future<void> main() async {
  final bloc = CounterBloc();
  final subscription = bloc.stream.listen(print); // 1
  bloc.add(CounterIncrementPressed());
  await Future.delayed(Duration.zero);
  await subscription.cancel();
  await bloc.close();
}

So sánh Cubit với Bloc

Lợi ích của Cubit

Một trong những lợi ích lớn nhất khi sử dụng Cubit đó là tính đơn giản của nó. Khi tạo 1 Cubit, chúng ta chỉ cần định nghĩa State và chức năng làm thay đổi state. Ngược lại, khi khởi tạo 1 Bloc, chúng ta cần phải định nghĩa các Event, State và triển khai các hàm EventHandle. Vì thế có thể thấy Cubit đơn giản hơn, triển khai ít code hơn.

Ngoài ra, khi sử dụng Cubit, chúng ta có thể gọi emit từ bất kỳ đâu để kích hoạt thay đổi trạng thái.

Lợi ích của Bloc

  • Truy vết được nguồn gốc state thay đổi dựa vào event nào
  • Chuyển đổi event dựa vào EventTransformer: debounceTime, throttle,

Flutter Bloc

Bloc Widgets

BlocBuilder

BlocBuilder là 1 widget, yêu cầu 1 Bloc và 1 builder. BlocBuider sử dụng để build lại widget khi state thay đổi.

BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // provide the local bloc instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  },
);

BlocSelector

BlocSelector giống BlocBuilder nhưng chỉ lắng nghe thay đổi của 1 giá trị đặc biệt nào đó trong state, sử dụng để ngăn chặn việc build lại nếu giá trị đó không thay đổi.

BlocSelector<BlocA, BlocAState, SelectedState>(
  selector: (state) {
    // return selected state based on the provided state.
  },
  builder: (context, state) {
    // return widget here based on the selected state.
  },
);

BlocProvider

BlocProvider là 1 widget cung cấp bloc cho con của nó trong 1 cây widget con. Thường sẽ sử dụng BlocProvider để tạo 1 bloc mới và các con của nó sẽ sử dụng chung 1 bloc này.

BlocProvider(
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);
BlocProvider(
  lazy: false,
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

Mặc định BlocProvinder sẽ tạo bloc lazy, nghĩa là hàm create được chạy khi bloc được sử dụng. Để khởi tạo luôn bloc, set tham số lazy: false

MultiBlocProvider

MultiBlocProvider là 1 widget gộp nhiều BlocProvider. Thay vì tạo nhiều BlocProvider lồng nhau thì MultiBlocProvider sẽ gom cụm bloc vào provinders

BlocProvider<BlocA>(
  create: (BuildContext context) => BlocA(),
  child: BlocProvider<BlocB>(
    create: (BuildContext context) => BlocB(),
    child: BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
      child: ChildA(),
    ),
  ),
);

thành

MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
);

BlocListener

BlocListener là 1 widget dùng để lắng nghe sự thay đổi của state trong bloc và xử lý các logic không nằm trong widget tree như navigation, show SnackBar, Dialog,…

BlocListener<BlocA, BlocAState>(
  bloc: blocA,
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: const SizedBox(),
);

MultiBlocListener

MultiBlocListener là 1 widget gộp nhiều BlocListener.

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {},
  child: BlocListener<BlocB, BlocBState>(
    listener: (context, state) {},
    child: BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
      child: ChildA(),
    ),
  ),
);

thành

MultiBlocListener(
  listeners: [
    BlocListener<BlocA, BlocAState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocB, BlocBState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
    ),
  ],
  child: ChildA(),
);

BlocConsumer

BlocConsumer là tổng hợp của BlocListener và BlocBuilder, sử dụng khi vừa cập nhật lại UI, vừa xử lý logic khác khi state thay đổi

BlocConsumer<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  },
);

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Related Posts

Bloc Pattern cơ bản trong Flutter

1. Bloc Pattern là gì Bloc Pattern là một Pattern :D, mục đích chính của Pattern này là tách code business logic ra khỏi UI thay vì gộp chung logic…

Read more

Stream trong Flutter

Stream là gì? Lập trình bất đồng bộ là một thuật ngữ phổ biến trong lập trình. Trong ngôn ngữ Dart, Future class để khai báo 1 hàm bất đồng…

Read more
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất
Phản hồi nội tuyến
Xem tất cả bình luận