BLoC(Business Logic Component) — Google이 2018년 DartConf에서 발표한 Flutter용 상태 관리 패턴입니다. BLoC는 반응형 스트림(Stream)을 통해 비즈니스 로직과 사용자 인터페이스를 분리합니다. UI가 Event를 보내면 BLoC가 이를 처리하고 Stream을 통해 새 State를 반환합니다. pub.dev에 따르면 flutter_bloc 패키지는 11,000개 이상의 좋아요를 받았으며 수천 개의 Flutter 애플리케이션에서 사용됩니다.
주요 포인트
BLoC(Business Logic Component) — 비즈니스 로직을 UI와 분리된 별도 클래스로 추출하는 Flutter용 아키텍처 패턴입니다. BLoC는 이벤트 스트림(Event)을 통해 입력 데이터를 받고 상태 스트림(State)을 통해 출력 데이터를 생성합니다. 프레젠테이션 계층(Widget)은 State 스트림을 구독하고 UI를 렌더링할 뿐, 직접 비즈니스 로직을 실행하지 않습니다.
BLoC 개념은 반응형 프로그래밍과 Observer 패턴에 기반합니다. 각 BLoC 구성 요소는 명확한 계약을 가진 별도 모듈입니다. 알려진 Event 세트(무슨 일이 일어날 수 있는지)와 알려진 State 세트(무엇이 표시될 수 있는지)가 있습니다. 개발자는 UI에서 "실수로" 상태를 변경할 수 없습니다 — 특정 Event를 통해서만 가능합니다. 이렇게 하면 코드를 예측 가능하고 테스트 가능하게 만듭니다.
Flutter Community 2025 설문조사에 따르면 BLoC는 Provider 다음으로 Flutter에서 가장 인기 있는 상태 관리 솔루션 2위를 차지했습니다. 주요 장점: 강력한 타입 지정, 로직 분리, 내장 Stream 지원, 풍부한 유틸리티 에코시스템(BlocProvider, BlocListener, BlocSelector).
BLoC 아키텍처는 세 가지 엔터티를 중심으로 구축됩니다: Event(입력), Bloc(처리기), State(출력). Widget이 add() 메서드를 통해 Event를 보냅니다. Bloc이 mapEventToState 또는 on<Event> 메서드에서 Event를 받아 비즈니스 로직을 실행하고 yield를 통해 새 State를 내보냅니다. Widget은 Stream을 통해 State를 받고 재구성합니다.
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}타입 안전성: Bloc는 Event와 State의 두 가지 타입으로 매개변수화됩니다. Dart 컴파일러는 Widget이 선언된 Event만 호출하고 Bloc이 선언된 State만 반환하는지 확인합니다. "알 수 없는 작업"과 같은 런타임 오류가 제거됩니다.
Close 및 Dispose: Bloc는 Closeable 인터페이스를 구현합니다. 위젯이 소멸되면 Bloc는 자동으로 close() 메서드를 통해 Stream을 닫습니다. 반응형 구독 누수는 불가능합니다 — BlocProvider가 Bloc의 수명 주기를 관리하여 라우트나 페이지에 바인딩합니다.
Cubit은 flutter_bloc 6.0 패키지에서 도입된 Event 없는 Bloc의 간소화된 구현입니다. Cubit은 Event 클래스 대신 직접 메서드를 선언합니다: increment(), fetchData(). 내부적으로 Cubit은 동일한 Stream 기반 메커니즘을 사용하지만 Event 계층을 숨깁니다. 이는 간단한 시나리오에서 상용구를 40-50% 줄여줍니다.
| 특성 | Bloc | Cubit |
|---|---|---|
| Event 클래스 | 필수 | 불필요 |
| 상용구 | 높음 | 낮음 |
| 액션 추적 | Event 타입을 통해 | 메서드 이름만 |
| 적합한 대상 | 복잡한 시나리오 | 간단한 상태 |
| 분석 | Event로 자동 | 수동 |
Cubit을 선택해야 하는 경우: 2-3가지 변형이 있는 상태(loading, loaded, error), 간단한 폼, 카운터, UI 상태(열림/닫힘). Bloc를 선택해야 하는 경우: 여러 액션이 있는 복잡한 비즈니스 로직: 주문 처리, 인증, 데이터 동기화. Bloc는 Event를 통해 각 액션의 상세 추적을 제공합니다 — 모든 호출이 BlocObserver에 기록됩니다.
BlocObserver — 애플리케이션의 모든 Bloc와 Cubit을 추적하는 전역 관찰자입니다. Event, State, 오류 및 전환을 기록할 수 있습니다. 인스턴스 하나만 연결하면 됩니다: Bloc.observer = AppBlocObserver(), 그러면 전체 애플리케이션 상태 추적을 중앙에서 사용할 수 있습니다.
BlocProvider — flutter_bloc의 InheritedWidget으로, 자식 위젯에 Bloc를 제공합니다. 위젯이 초기화되면 BlocProvider가 Bloc를 생성하고, 소멸되면 close()를 통해 자동으로 닫습니다. BlocProvider는 MaterialApp 수준(전역 Bloc) 또는 특정 라우트 수준(로컬 Bloc)에 배치할 수 있습니다.
BlocProvider(
create: (context) => CounterBloc(),
child: Column(
children: [
BlocBuilder<CounterBloc, int>(
builder: (context, state) => Text('$state'),
),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('+'),
),
],
),
)BlocBuilder — 새 State마다 UI를 재구성하는 위젯입니다. BlocListener — 부작용 처리용(UI 재구성 없이 State를 한 번 처리): SnackBar 표시, 다른 화면으로 이동. BlocConsumer — 재구성과 부작용이 모두 필요한 경우를 위한 Builder와 Listener의 조합. BlocSelector — State의 특정 필드가 변경될 때만 선택적으로 재구성합니다.
MultiBlocProvider — 중첩 수준을 높이지 않고 중첩된 BlocProvider를 위한 위젯입니다. 10-15개의 Bloc가 있는 Flutter 애플리케이션은 루트 수준에서 MultiBlocProvider를 사용하여 전체 애플리케이션에서 사용 가능한 모든 Bloc(AuthenticationBloc, CartBloc, SettingsBloc)을 등록합니다.
BLoC 테스트는 Flutter 위젯 없이 격리하여 수행됩니다. Dart 패키지 flutter_test와 bloc_test 패키지를 가져오기만 하면 됩니다. 테스트 시나리오: Bloc 생성, Event 추가, State 확인. blocTest — build → act → expect 시퀀스를 자동화하는 유틸리티입니다.
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
)모킹: 리포지토리나 API에 의존하는 Bloc는 mocktail을 통해 모의 객체로 테스트됩니다. 리포지토리는 추상화 수준에서 모킹되고, Bloc는 생성자를 통해 모의된 의존성을 받습니다. Hydrated Bloc — 로컬 저장소에 자동 상태 저장/복원을 위한 확장입니다. HydratedBlocStorage와 임시 파일 저장소로 테스트됩니다.
폴더 및 파일: BLoC를 사용하는 일반적인 Flutter 프로젝트 구조: bloc/counter_bloc.dart, bloc/counter_event.dart, bloc/counter_state.dart. 30개 이상의 화면의 경우 기능 기반 그룹화가 권장됩니다: features/auth/bloc/, features/cart/bloc/. 각 Bloc는 별도 파일, 각 Event와 State는 별도 파일 또는 Bloc와 같은 파일에 있습니다.
성능: BLoC는 빈 Stream에 오버헤드를 만들지 않습니다. BlocBuilder는 buildWhen을 사용하여 재구성을 필터링합니다 — 특정 조건이 변경될 때만 위젯이 업데이트됩니다. Close는 비활성 Bloc가 메모리를 소비하지 않도록 보장합니다. Flutter DevTools에 따르면 BLoC는 번들 크기에 1% 미만만 추가합니다.
Provider에서 마이그레이션: BLoC는 동일한 프로젝트에서 Provider와 쉽게 공존합니다. 점진적 마이그레이션: 먼저 가장 복잡한 Provider를 Bloc로 교체한 다음 나머지를 교체합니다. BlocProvider는 Provider 트리와 호환됩니다: 이전 위젯은 Provider를, 새 위젯은 BlocProvider를 동일한 애플리케이션 내에서 사용할 수 있습니다.
자주 묻는 질문
BLoC는 비즈니스 로직 분리와 강력한 타입 지정을 위해 Event + Stream을 사용합니다. Provider는 간단한 의존성 주입과 ChangeNotifier를 위한 InheritedWidget의 래퍼입니다. BLoC는 여러 상태가 있는 복잡한 시나리오에 더 적합하고, Provider는 로컬 UI 상태에 적합합니다. BLoC는 더 많은 상용구가 필요하지만 Event를 통한 완전한 추적성을 제공합니다.
Hydrated Bloc는 hydrated_bloc 패키지의 확장으로, 마지막 State를 로컬 저장소(기본적으로 Hive)에 자동으로 저장합니다. 애플리케이션이 다시 시작되면 Bloc는 초기 상태 대신 저장된 상태를 복원합니다. 이렇게 하면 수동 저장 호출 없이 지속성 문제를 해결합니다: 로그인, 장바구니, 설정이 세션 간에 자동으로 저장됩니다.
BLoC의 오류는 mapEventToState 또는 on<Event> 내에서 try-catch를 통해 처리됩니다. 오류가 발생하면 Bloc는 오류 State를 반환합니다: yield LoadError(error.message). UI에서 BlocListener 또는 BlocConsumer가 State의 오류 유형을 확인하고 SnackBar 또는 대화상자를 표시합니다. BlocObserver는 처리되지 않은 모든 예외를 전역적으로 기록합니다.
BLoC는 Dart Stream과 Flutter 위젯을 사용하므로 Flutter 특화 패턴입니다. Event → Bloc → State 개념은 AngularDart 및 Server-side Dart에 적용할 수 있지만, 핵심 에코시스템(BlocProvider, BlocBuilder, BlocObserver)은 Flutter에 종속됩니다. React Native의 경우 Redux 또는 MobX를, SwiftUI의 경우 Combine + MVVM을 사용하세요.
Cubit — 간단한 상태(카운터, 토글, 2-3개 필드 폼)용. Bloc — 복잡한 로직(뉴스 피드, 주문 처리, 인증)용. 주요 규칙: 분석이나 디버깅을 위해 각 액션(Event)의 추적이 필요한 경우 Bloc를 선택하세요. 상태를 변경하는 메서드로 충분하다면 Cubit을 선택하세요. 두 패턴 모두 동일한 프로젝트에서 공존할 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.