블록? -> 상태관리를 위해 사용하는 디자인 패턴. Flutter에서는 bloc패키지와 flutter_bloc패키지를 이용해서 쉽게 사용할 수 있다.
간단하게 GetX 대비 장 / 단점을 나열해보자면
정도로 요약할 수 있다.
이제 예제를 통해 사용법을 알아보도록하자.
이 글에서 다루는 예제는 Bloc을 구현하기 위한 패턴을 구현하는 것 보다는 bloc, flutter_bloc 패키지를 통해 어떻게 bloc을 활용할 수 있을까에 대해 다뤄보고자 한다.
우선 이번 예제에서 사용할 데이터 모델을 정의한다.
항상 모든 예제들이 불편했던 부분 중 하나가 int, String같은 단순 데이터를 가지고 예시를 들었는데 실제로 개발을 하다보면 중첩된 List, Map 등 복잡한 자료구조가 사용되기 때문에 이번 예제에서는 먼저 데이터 모델을 정의 하고자 한다.
class User {
final String name;
final int age;
User({this.name = '', this.age=0});
}
이제 Flutter프로젝트에서 bloc과 flutter_bloc을 다운받는다.
$ flutter pub add bloc flutter_bloc
이제 bloc을 구현하는데, bloc을 구현할 때는 Bloc혹은 Cubit을 상속받아 구현한다. 이번 예제는 Bloc을 상속받아 구현해보려고한다.
class UserBloc extends Bloc<UserEvent, User> {
UserBloc() : super(User()) {
//이벤트 리스너
on<CreateUserEvent>(createUser);
}
// 이벤트 리스너 구현부
FutureOr<void> createUser(CreateUserEvent event, Emitter<User> emit) {
final User user = User(name: event.name, age: int.tryParse(event.age) ?? 0);
emit(user);
}
}
// 이벤트 정의
abstract class UserEvent {}
class CreateUserEvent extends UserEvent {
final String name;
final String age;
CreateUserEvent({required this.name, required this.age});
}
예제의 Bloc은 UserEvent와 User를 상속받는다. UserEvent는 UI에서 Bloc으로 전달되는 이벤트를 정의한 것이고 User는 Bloc이 상태를 관리하려는 객채가 된다.
이벤트를 클래스로 정의한 이유는 클래스 내부에 변수를 선언해 둠으로서 각 이벤트마다 조건과 파라미터를 정의할 수 있는 점이 편리하기 때문이다. enum이나 다른 것들로 이벤트를 정의해도 상관없다.
이러한 이벤트들을 Bloc객체 생성 시 선언된 on함수를 통해 감지하고, on함수의 구현부에서 emit을 통해 상태를 변화시키는 것이 Bloc의 기본적인 동작이다.
실제로 동작하는 것을 확인해보기 위해 UI를 구현한다.
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (context) => UserBloc(),
child: Home(),
),
);
}
}
class Home extends StatelessWidget {
Home({super.key});
TextEditingController nameController = TextEditingController();
TextEditingController ageController = TextEditingController();
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(title: const Text('Test')),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
TextFormField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
TextFormField(
controller: ageController,
decoration: const InputDecoration(labelText: 'Age'),
),
const SizedBox(height: 24.0),
BlocBuilder<UserBloc, User>(
builder: (context, state) {
return Column(
children: [
Text('Name : ${state.name}'),
Text('Age : ${state.age}'),
],
);
},
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: () {
BlocProvider.of<UserBloc>(context).add(
CreateUserEvent(
name: nameController.text,
age: ageController.text,
),
);
},
child: const Text('Generate User'),
),
],
),
),
),
),
);
}
}
BlocBuilder<UserBloc, User>는 builder를 통해 제네릭으로 선언된 Bloc의 상태가 변화하면 화면을 다시 그리는 역할을 한다.
BlocProvider는 현재 위젯의 BuildContext를 통해 Bloc을 찾는 역할을 하며 add함수를 통해 이벤트를 전달한다.