Flutter에서의 BLoC(Business Logic Component)은 애플리케이션의 비즈니스 로직을 UI와 분리하여 유지보수성, 테스트 용이성, 재사용성을 높이기 위해 사용하는 상태 관리(State Management) 아키텍처 패턴이다.
1. BLoC이란 무엇인가?
https://bloclibrary.dev/ko/why-bloc/
왜 Bloc인가?
어떤 요소가 Bloc을 견고한 상태 관리 솔루션으로 만드는 지에 대한 개요입니다.
bloclibrary.dev
BLoC (Business Logic Component) 패턴은 Reactive Programming (반응형 프로그래밍)을 기반으로 하며, Dart의 Stream과 Sink를 사용하여 UI와 비즈니스 로직을 분리한다.
- Input: 사용자의 이벤트 (예: 버튼 클릭)를 Sink에 전달
- Output: 처리된 결과를 Stream을 통해 UI에 전달
Bloc은 다음 세 가지 핵심 가치를 염두에 두고 설계되었다:
- 간단함: 이해하기 쉽고 다양한 스킬 수준을 가진 개발자가 사용할 수 있다.
- 강력함: 더 작은 구성 요소로 구성하여 놀랍고 복잡한 애플리케이션을 만드는 데 도움을 준다.
- 테스트 가능: 애플리케이션의 모든 측면을 쉽게 테스트할 수 있으므로 자신 있게 테스트를 반복할 수 있다.
전반적으로, Bloc은 상태 변경이 발생할 수 있는 시기를 규제하고 애플리케이션 전체에 걸쳐 단일한 상태를 변경하는 방법을 시행함으로써 상태 변경을 예측 가능하게 만들려고 시도한다.
Streams
- Stream은 연속적인 비동기 데이터다.
- Streams 을 쉽게 이해하기 위해서는 물이 흐르는 파이프를 생각하자. 파이프는 Streams이고 물은 비동기 데이터이다.
async* (비동기 생성기) 함수를 작성하여 Dart에서 Stream을 생성할 수 있다.
Stream<int> countStream(int max) async* {
for (int i = 0; i < max; i++) {
yield i;
}
}
- 함수를 async*로 표시하면 yield 키워드를 사용하여 데이터의 Stream을 반환할 수 있다. 위 예시에서는 max 정수 파라미터까지의 정수 Stream을 반환하고 있다.
- async* 함수에서 yield 할 때 마다 해당 데이터를 Stream을 통해 푸시한다.
Cubit
cubit 은 BlocBase를 extends 한 클래스로, 모든 유형의 state를 관리하도록 확장할 수 있다. 즉 BLoC의 간단한 버전이다. flutter_bloc 패키지에서 제공하며 Stream 기반이다. 이벤트 클래스를 따로 만들지 않고, 메서드 호출 -> 상태 변경을 하는 구조다.
2. BLoC vs Cubit
항목 | BLoC | Cubit |
이벤트 (Event) | 반드시 정의 필요 | 없음 |
코드 구조 | Event → Bloc → State | 직접 메서드 호출로 상태 변경 |
복잡도 | 복잡한 상태 흐름에 적합 | 간단한 로직에 적합 |
Stream 기반 | on<Event>((e, emit) {...}) | emit(State) 직접 호출 |
유즈케이스 | 로그인, 결제, 인증 등 다중 이벤트 로직 | 토글, 카운터, 단순 상태 변경 |
BLoC 작동 흐름
[UI] --이벤트--> [Bloc] --로직처리--> [State] --> [UI]
예: 로그인 버튼 클릭 → 로그인 Bloc에 이벤트 전달 → 로그인 로직 수행 → 결과 상태 방출 → UI가 상태에 따라 리렌더링
BLoC 사용 예시
1. 이벤트 정의
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
2. 상태 정의
class CounterState {
final int count;
const CounterState(this.count);
}
3. BLoC 구현
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<Increment>((event, emit) => emit(CounterState(state.count + 1)));
on<Decrement>((event, emit) => emit(CounterState(state.count - 1)));
}
}
4. UI에서 사용
BlocProvider(
create: (_) => CounterBloc(),
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Column(
children: [
Text('Count: ${state.count}'),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('Increment'),
),
],
);
},
),
)
Cubit 사용 예시
1. 상태 정의
class CounterState {
final int count;
const CounterState(this.count);
}
2. cubit 클래스 정의
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState(0));
void increment() => emit(CounterState(state.count + 1));
void decrement() => emit(CounterState(state.count - 1));
}
3. UI에서 사용
BlocProvider(
create: (_) => CounterCubit(),
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Column(
children: [
Text('${state.count}'),
ElevatedButton(
onPressed: () => context.read<CounterCubit>().increment(),
child: Text('증가'),
)
],
);
},
),
);
3. 실제 서비스에서의 사용 예
예시 1: 로그인 흐름(BLoC)
이벤트: LoginButtonPressed
↓
BLoC: 로그인 시도, 서버 요청
↓
상태: LoginLoading → LoginSuccess / LoginFailure
↓
UI: 로딩 표시 / 에러 메시지 / 홈 화면 이동
예시 2: 다크모드 토글(Cubit)
Cubit: ThemeCubit → toggleDarkMode()
↓
상태: DarkModeOn / DarkModeOff
↓
UI: 테마 변경
'앱 개발 > Flutter' 카테고리의 다른 글
[Flutter] 입문 - 시작과 기초 개념 잡기 (2) | 2025.07.25 |
---|---|
[Flutter] 입문 - 화면 전환(Navigation) (1) | 2025.07.25 |
[Flutter] 입문 - 기본 위젯 & Stateful vs Stateless 이해 (0) | 2025.07.25 |
[Flutter] Form 알아보기 (4) | 2025.07.24 |