카테고리 없음
[Flutter] Bloc 상태관리: 이벤트 기반 구조로 상태 다루기
BreadDev
2025. 6. 27. 16:58
728x90
Flutter 상태관리에서 Bloc은 이벤트를 통해서만 상태를 변경하는 방식 입니다. 이번에는 코드를 통해 Bloc의 이벤트 기반 구조와 작동 방식을 공부해 보겠습니다.
Bloc의 이벤트 중심 구조
Bloc에서는 상태를 직접 변경하지 않고 "이벤트"를 통해서만 변경합니다.
사용자 액션 → 이벤트 발송 → Bloc 처리 → 새로운 상태 → UI 업데이트
1. 이벤트 정의: 무엇이 일어날 수 있는가
sealed class CounterEvent {}
final class CounterUpEvent extends CounterEvent {}
final class CounterDownEvent extends CounterEvent {}
sealed class의 특징
CounterEvent 추상 클래스:
- sealed: 이 파일에서만 상속 가능
- 제한된 하위 타입: CounterUpEvent, CounterDownEvent만 존재
- 명확한 이벤트 목록: 이 앱에서 발생할 수 있는 모든 이벤트가 한눈에 보임
구체적인 이벤트들:
final class CounterUpEvent extends CounterEvent {} // "증가 버튼이 눌렸다"
final class CounterDownEvent extends CounterEvent {} // "감소 버튼이 눌렸다"
각 이벤트는 "무엇이 일어났는지"만 나타냅니다. 어떻게 처리할지는 Bloc에서 결정해요.
2. 상태 클래스: 어떤 데이터를 관리하는가
class BlocCounterState {
int count;
BlocCounterState({required this.count});
}
단순하지만 명확한 구조
3. Bloc: 이벤트를 받아서 상태로 변환
class CounterBloc extends Bloc<CounterEvent, BlocCounterState> {
CounterBloc() : super(BlocCounterState(count: 0)) {
on<CounterUpEvent>((event, emit){
emit(BlocCounterState(count: state.count + 1));
});
on<CounterDownEvent>((event, emit){
emit(BlocCounterState(count: state.count - 1));
});
}
}
Bloc 클래스 구조 분석
Bloc<CounterEvent, BlocCounterState> 상속:
- 첫 번째 제네릭: 처리할 이벤트 타입
- 두 번째 제네릭: 관리할 상태 타입
- 타입 안전성: 잘못된 타입 사용 시 컴파일 오류
생성자와 초기 상태:
CounterBloc() : super(BlocCounterState(count: 0))
- super() 호출: 부모 클래스에 초기 상태 전달
- count: 0: 앱 시작 시 카운터는 0부터 시작
이벤트 핸들러 등록
on<CounterUpEvent> 핸들러:
on<CounterUpEvent>((event, emit){
emit(BlocCounterState(count: state.count + 1));
});
- 특정 이벤트만 처리: CounterUpEvent가 발생했을 때만 실행
- event 매개변수: 발생한 이벤트 인스턴스 (여기서는 사용하지 않음)
- emit 함수: 새로운 상태를 방출하는 함수
- state.count + 1: 현재 상태의 count에서 1 증가
on<CounterDownEvent> 핸들러:
on<CounterDownEvent>((event, emit){
emit(BlocCounterState(count: state.count - 1));
});
- 동일한 패턴: 감소 이벤트를 처리
- state.count - 1: 현재 상태의 count에서 1 감소
emit() 함수의 역할
emit(BlocCounterState(count: state.count + 1));
- 새로운 인스턴스 생성: 기존 상태를 변경하지 않음
- 자동 알림: emit 호출 시 구독 중인 UI에 자동으로 알림
- 상태 업데이트: 이 새로운 상태가 현재 상태가 됨
4. UI: BlocBuilder로 상태 구독
class BlocCounterHomePage extends StatelessWidget {
BlocCounterHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Counter App')),
body: SafeArea(
child: Center(
child: BlocBuilder<CounterBloc, BlocCounterState>(
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 10,
children: [
Text(
'Counter.count : ${state.count}',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
ElevatedButton(
onPressed: () {
context.read<CounterBloc>().add(CounterUpEvent());
},
child: Text('카운트 증가'),
),
ElevatedButton(
onPressed: () {
context.read<CounterBloc>().add(CounterDownEvent());
},
child: Text('카운트 감소'),
),
],
);
}
)
),
),
);
}
}
BlocBuilder<CounterBloc, BlocCounterState>
자동 구독 시스템:
- 상태 변화 감지: CounterBloc의 상태가 변경될 때마다 감지
- builder 함수 호출: 새로운 상태로 builder 함수 재실행
- state 매개변수: 현재 상태 인스턴스 (BlocCounterState)
상태 표시
Text('Counter.count : ${state.count}')
- state.count 접근: 현재 상태의 count 값 표시
- 자동 업데이트: 상태가 변경되면 자동으로 새로운 값 표시
이벤트 발송
context.read<CounterBloc>().add(CounterUpEvent());
단계별 분석:
- context.read<CounterBloc>(): Provider를 통해 CounterBloc 인스턴스 가져오기
- .add(): Bloc에 이벤트 추가
- CounterUpEvent(): 증가 이벤트 인스턴스 생성하여 전달
5. 앱 설정: BlocProvider
class BlocCounterApp extends StatelessWidget {
const BlocCounterApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: BlocProvider(
create: (_) => CounterBloc(),
child: BlocCounterHomePage(),
),
);
}
}
BlocProvider의 역할
의존성 주입:
- create: (_) => CounterBloc(): CounterBloc 인스턴스 생성
- child 범위: BlocCounterHomePage와 그 하위 위젯에서 접근 가능
- 자동 관리: 위젯이 dispose될 때 Bloc도 자동으로 정리
실제 동작 시나리오
증가 버튼을 눌렀을 때
1. 사용자가 "카운트 증가" 버튼 클릭
↓
2. onPressed: () { context.read<CounterBloc>().add(CounterUpEvent()); }
↓
3. CounterBloc의 on<CounterUpEvent> 핸들러 실행
↓
4. emit(BlocCounterState(count: state.count + 1)) 호출
↓
5. BlocBuilder가 새로운 상태 감지
↓
6. builder 함수 재실행으로 Text 위젯 업데이트
상태 변화 추적
// 초기 상태
BlocCounterState(count: 0)
// CounterUpEvent 후
BlocCounterState(count: 1)
// CounterDownEvent 후
BlocCounterState(count: 0)
// 다시 CounterUpEvent 후
BlocCounterState(count: 1)
Bloc의 구조적 특징들
이벤트 중심 설계
// 상태 직접 변경 불가능
// counterBloc.state.count = 5;
// 이벤트를 통해서만 변경
counterBloc.add(CounterUpEvent());
타입 안전성
// 잘못된 이벤트 타입
counterBloc.add("wrong event"); // 컴파일 오류
// sealed class로 제한된 이벤트만 가능
counterBloc.add(CounterUpEvent()); // 정상
상태의 불변성
// 기존 상태 수정
// state.count += 1;
// 새로운 상태 인스턴스 생성
emit(BlocCounterState(count: state.count + 1));
책임 분리
- Event: 무엇이 일어났는지 정의
- State: 어떤 데이터를 관리하는지 정의
- Bloc: 이벤트를 받아 상태로 변환하는 로직
- UI: 상태 표시 및 이벤트 발송
마무리
Bloc은 이벤트를 통해서만 상태를 변경하는 구조를 가지고 있습니다.
Bloc의 핵심 구성 요소:
- sealed class Event: 발생 가능한 이벤트 타입들
- State 클래스: 관리할 상태의 구조
- Bloc 클래스: 이벤트 핸들러와 상태 변경 로직
- BlocBuilder: 상태 구독과 UI 업데이트
- context.read().add(): 이벤트 발송
Bloc의 이벤트 기반 흐름
사용자 액션 → 이벤트 발송 → Bloc 처리 → 상태 방출 → UI 업데이트