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ọilisten
trongCubit
. Ví dụ, khicubit.increment()
được gọi trước khi khởi tạosubscription
, state vẫn sẽ thay đổi nhưng không có đượcprint
ra. Cácstate
mới cũng không được nhận sau khicancel
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
},
);