: 프레젠테이션과 비즈니스 로직을 쉽게 구분할 수 있어 코드가 빠르고, 테스트하기 쉽고, 재사용이 가능해짐
3개의 레이어로 구분한다.
: Bloc State에 따라 유저에게 어떻게 보여줄지를 결정한다.
: Bloc layer의 역할은 Presentation Layer으로 부터 발생한 event를 새로운 State로 바꾼다.
class MyBloc extends Bloc {
final OtherBloc otherBloc;
late final StreamSubscription otherBlocSubscription;
MyBloc(this.otherBloc) {
otherBlocSubscription = otherBloc.stream.listen((state) {
// React to state changes here.
// Add events here to trigger changes in MyBloc.
});
}
Future<void> close() {
otherBlocSubscription.cancel();
return super.close();
}
}: 소스로부터 데이터를 찾고 조절함.
가장 낮은 Layer로 데이터베이스, 네트워크 요청, 비동기 데이터 소스와 상호작용함
BlocSubject + Noun (optional) + Verb (event)sealed class CounterEvent {}
final class CounterStarted extends CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}
final class CounterIncrementRetried extends CounterEvent {}sealed class CounterEvent {}
final class Initial extends CounterEvent {}
final class CounterInitialized extends CounterEvent {}
final class Increment extends CounterEvent {}
final class DoIncrement extends CounterEvent {}
final class IncrementCounter extends CounterEvent {}BlocSubject + Verb (action) + Statesealed class CounterState {}
final class CounterInitial extends CounterState {}
final class CounterLoadInProgress extends CounterState {}
final class CounterLoadSuccess extends CounterState {}
final class CounterLoadFailure extends CounterState {}sealed class CounterState {}
final class Initial extends CounterState {}
final class Loading extends CounterState {}
final class Success extends CounterState {}
final class Succeeded extends CounterState {}
final class Loaded extends CounterState {}
final class Failure extends CounterState {}
final class Failed extends CounterState {}: 비동기 데이터들의 시퀀스
Stream<int> countStream(int max) async* {
for (int i = 0; i < max; i++) {
yield i;
}
}
// 수형 파라미터인 max값까지 정수들의 Stream을 반환
async*함수를 이용하면 Stream이 생성 가능함yield 키워드를 사용해서 데이터의 Stream을 변환할 수 있음async* 함수에서 yield를 할 때마다, Stream으로 푸시Stream사용
Future<int> sumStream(Stream<int> stream) async {
int sum = 0;
await for (int value in stream) {
sum += value;
}
return sum;
}
// 스트림의 각 값들을 기다리고 모든 정수들의 합을 반환
void main() async {
/// Initialize a stream of integers 0-9
Stream<int> stream = countStream(10);
/// Compute the sum of the stream of integers
int sum = await sumStream(stream);
/// Print the sum
print(sum); // 45
}
: BlocBase를 확장한 클래스로 어떤 타입의 생태라도 관리할 수 있도록 확장할 수 있다.

Cubit은 상태의 변화를 트리거하는 함수를 가지고 있다.Cubit 의 출력이며 애플리케이션의 상태의 일부를 나타냄 → UI 구성 요소들은 상태에 대한 정보를 받고, 현재 상태를 기반으로 스스로를 다시 그림예제)
CounterCubit
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
}
// Cubit이 관리할 상태의 타입을 정의
// 위의 CounterCubit의 경우에, 상태는 int로 표현되지만
// 더 복잡한 경우에는 primitive 타입 대신에 class를 사용함
초기값 정의
class CounterCubit extends Cubit<int> {
CounterCubit(int initialState) : super(initialState);
}
// 위 CounterCubit 와 달리 외부에서 initialState 값을 받을 수 있다
인스턴스
final cubitA = CounterCubit(0); // state starts at 0
final cubitB = CounterCubit(10); // state starts at 10
: 각 Cubit은 emit을 사용해 새로운 상태 값을 만들 수 있음
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
**void increment() => emit(state + 1);**
// 외부에서 CounterCubit에게 상태 값의 증가를 알릴 수 있게 하는 메소드
// increment가 호출될 때, state getter를 이용해 Cubit의 현재 상태에 접근할 수 있고
// 현재 상태에 1을 더함으로써 새로운 상태를 emit 함
}
void main() {
final cubit = CounterCubit();
print(cubit.state); // 0
cubit.increment();
print(cubit.state); // 1
cubit.close(); // 내부의 상태 스트림을 닫기 위해 Cubit에 close를 호출
}
: 실시간 상태 업데이트를 수신할 수 있게 해주는 Stream을 가지고 있음
Future<void> main() async {
final cubit = CounterCubit();
**final subscription = cubit.stream.listen(print);
//**CounterCubit을 subscribe하고 각 상태 변화를 출력함
cubit.increment(); // 새로운 상태를 emit하는 increment 함수를 호출
await Future.delayed(Duration.zero);
await subscription.cancel();
await cubit.close();
}
: Cubit이 새로운 상태를 emit 할 때, Change가 발생함. onChange를 재정의함으로써, Cubit의 모든 변화를 관찰할 수 있음.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
}
Change는 Cubit의 상태 업데이트가 이루어진 후 발생한다.Change는 currentState와 nextState로 구성된다.void main() {
CounterCubit()
..increment()
..close();
}
[출력 결과] Change { currentState: 0, nextState: 1 }
: Changes 를 한 곳에서 접근하는 것이 가능함
→모든 Changes에 응답하고 싶다면 Bloc Observer를 구현하면 됨
class SimpleBlocObserver extends BlocObserver {
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('${bloc.runtimeType} $change');
}
}
// BlocObserver를 상속받고 onChange메소드를 재정의하면 됨
void main() {
Bloc.observer = SimpleBlocObserver();
CounterCubit()
..increment()
..close();
}
Change { currentState: 0, nextState: 1 }
CounterCubit Change { currentState: 0, nextState: 1 }
Cubit 내부의 onChange 재정의가 가장 처음에 호출되고 이어서 BlocObserver의 onChange가 호출됩니다.
: 모든 Cubit은 에러 발생을 알려주는 메소드인 addError를 가지고 있음
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() {
addError(Exception('increment error!'), StackTrace.current);
emit(state + 1);
}
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
Cubit에 대해 모든 에러를 처리하고 싶다면, Cubit 내부에서 onError를 재정의하면 됨.class SimpleBlocObserver extends BlocObserver {
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('${bloc.runtimeType} $change');
}
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('${bloc.runtimeType} $error $stackTrace');
super.onError(bloc, error, stackTrace);
}
}
Bloc는 state 변화를 트리거하기 위해 함수가 아닌, events에 의존하는 고급 클래스Bloc는 BlocBase를 확장하고 그것은 Cubit과 비슷한 public API를 가진다는 것을 의미
Blocs는function을 호출하고 새로운state를 emit 하기 보다,
events를 수신하고 수신된 events를 출력될 states로 변환
CounterEvent
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
}
상태 변화 - on
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) {
emit(state + 1);
});
}
}
CounterIncrementPressed 이벤트를 관리하기 위해 EventHandler을 등록CounterIncrementPressd 이벤트에서 state getter와 emit(state+1)를 통해 bloc의 현재 상태이 접근이 가능Future<void> main() async {
final bloc = CounterBloc(); // 인스턴스 생성
print(bloc.state); // 0 - 초기상태 출력
bloc.add(CounterIncrementPressed()); // 이벤트 추가
await Future.delayed(Duration.zero);
print(bloc.state); // 1 - 상태 변화
await bloc.close(); // 스트림 종료
}
Future<void> main() async {
final bloc = CounterBloc();
final subscription = bloc.stream.listen(print); // 구독
bloc.add(CounterIncrementPressed()); // 이벤트 추가, 새로운 상태 emit
await Future.delayed(Duration.zero);
await subscription.cancel(); // 구독 수신 닫기
await bloc.close();
}
Cubit처럼, Bloc도 Stream의 특정한 타입이고 이는 Bloc 또한 subscribe하여 상태를 실시간으로 업데이트할 수 있다는 것을 의미sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
**
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}**
}
[출력예시] Change { currentState: 0, nextState: 1 }
현재 상태, 이벤트, 다음상태로 구성
**Transition { currentState: 0, event: Increment, nextState: 1 }**
*onTransition은 onChange 앞에 호출되며 currentState에서 nextState로의 변화를 어떤 이벤트가 트리거 했는지 포함
: 트랜지션을 관찰하고 싶다면 onTransition을 재정의 하면 됨
class SimpleBlocObserver extends BlocObserver {
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('${bloc.runtimeType} $change');
}
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('${bloc.runtimeType} $transition');
}
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('${bloc.runtimeType} $error $stackTrace');
super.onError(bloc, error, stackTrace);
}
}