bloc은 이벤트에 의존하여 상태 변경을 하는 진보된 클래스! 블록은 blocbase를 extends하며, 큐빗과 비슷한 api를 가지고 있음! / bloc은 이벤트를 수신하고 들어오는 이벤트를 나가는 상태로 변환!!

bloc을 만드는 것은 우리가 관리할 상태를 정의하는 것 외에 bloc이 처리할 수 있는 이벤트도 정의해야 함 (이부분 외에는 cubit과 비슷)
event는 블록에 대한 input / 일반적으로 버튼 누르기와 같은 사용자 상호 작용이나 page load와 같은 수명 주기 이벤트에 응답하여 추가!!
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
}
// 위에서 counter cubit을 만들 떄처럼 super를 통해 superclass에 전달하여 초기 상태를 지정!
블록은 큐빗의 함수가 아닌 on를 통해 이벤트 핸들러를 등록하도록 요구 => 이벤트 핸들러는 수신 이벤트를 0개 이상의 받는 상태로 변환하는 역할을 함
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) {
// handle incoming `CounterIncrementPressed` event
})
}
}
// 이벤트 핸들러는 추가된 이벤트와 수신 이벤트에 응답하여 0개 이상의 상태를 방출하는 데,
// 사용할 수 있는 emitter에 엑세스 할 수 있음!!
// 이벤트 핸들러를 업데이트 할 수 있다면, counterIncrementPressed 이벤트를 다룰 수 있음!!
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) {
emit(state + 1);
});
}
}
// 모든 카운터를 관라히기 위해 이벤트 핸들러 등록 => 들어오는 각 카운터에 대하여 incrementPressed 이벤트는
//상태 gettter 및 emit(state + 1)을 통해 블록의 현재 상태에 엑세스 할 수 있음!!
// bloc class는 blocbase를 확장하므로 cubit과 동일하게 state getter에서 언제든지 블록의 현재 상태에 엑세스 가능
// bloc은 절대 새로운 상태를 직접적으로 emit하면 안됌!
// 대신 이벤트 핸들러 내의 수신 이벤트에 대한 응답으로 모든 상태 변경 output해야함!
// 블록과 큐빗 모두 중복 상태 무시 / state와 nextstate를 emit하면 상태 변경이 발생하지 않음
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();
}
// counterbloc 인스턴스 생성 => 현재 상태 출력 (아직 새로운 상태 emit이 되지 않음) =>
//counter 추가 => incrementPressed 이벤트를 발생시켜, 상태 변경 트리거 =>
// 0 -> 1로 바뀐 bloc의 상태를 다시 print => bloc close => 내부 스트림 닫음
// 지연을 추가하여 이벤트 루프 반복을 기다림!!
큐빗과 마찬가지로 블록은 stream의 특수한 유형 / bloc에 구독하여 실시간으로 상태를 업데이트 할 수 있음
Future<void> main() async {
final bloc = CounterBloc();
final subscription = bloc.stream.listen(print); // 1
bloc.add(CounterIncrementPressed());
await Future.delayed(Duration.zero);
// 구독 취소가 바로 되지 않도록 delayed를 줌
await subscription.cancel();
await bloc.close();
}
// counterbloc subscription => 상태 변경에 대한 인쇄 호출 => counter 추가 =>
//incrementPressed event 발생 => on<Counter> trigger =>
//이벤트 처리 및 new state emit => update no want subscription.cancel * bloc close
bloc Observing (bloc extends blocCase하므로, change 사용하여 bloc의 모든 상태 변화를 observing 가능!)
abstract class CounterEvent {}
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);
}
}
void main() {
CounterBloc()
..add(CounterIncrementPressed())
..close();
}
// main에서 update함
Change { currentState: 0, nextState: 1 }
// 블록과 cubit의 한 가지 주요 차별화 요소는 bloc이 이벤트 중심이기에,
// 상태변화를 유발한 원인에 대한 정보도 포착할 수 있음 (onTransition을 overriding 하여, 작업 수행)
// 한 상태에서 다른 상태로의 변화를 전환 => 전환은 현재 상태, 이벤트 및 다음 상태로 구성!!
abstract class CounterEvent {}
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);
}
void onTransition(Transition<CounterEvent, int> transition) {
super.onTransition(transition);
print(transition);
}
}
Transition { currentState: 0, event: Increment, nextState: 1 }
// onTransition은 onChange 이전에 호출 / 현재 상태에서 다음 상태로 변경을 트리거한 이벤트 포함
Change { currentState: 0, nextState: 1 }
사용자 정의 blocObserver에서 전환 시 override 하여 단일 위치에서 발생하는 모든 전환을 관찰할 수 있음
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);
}
}
void main() {
BlocOverrides.runZoned(
() {
CounterBloc()
..add(CounterIncrementPressed())
..close();
},
blocObserver: SimpleBlocObserver(),
);
}
Transition { currentState: 0, event: Increment, nextState: 1 }
CounterBloc Transition { currentState: 0, event: Increment, nextState: 1 }
Change { currentState: 0, nextState: 1 }
CounterBloc Change { currentState: 0, nextState: 1 }
// onTransition 먼저 호출 (글로벌 이전 로컬) / 그 다음에 onChange 호출
// bloc instance의 또 다른 고유한 특징은 새로운 이벤트가 block 추가 시 호출되는 event 재정의할 수 있음
// onEvent도 전역뿐만 아니라 로컬에서 재정의할 수 있음
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
void onEvent(CounterEvent event) {
super.onEvent(event);
print(event);
}
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
void onTransition(Transition<CounterEvent, int> transition) {
super.onTransition(transition);
print(transition);
}
}
class SimpleBlocObserver extends BlocObserver {
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
print('${bloc.runtimeType} $event');
}
// onEvent는 이벤트가 추가되는 즉시 호출 // local onEvent는 blocObserver의 global onEvent 앞에 호출
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');
}
}
Increment
CounterBloc Increment
Transition { currentState: 0, event: Increment, nextState: 1 }
CounterBloc Transition { currentState: 0, event: Increment, nextState: 1 }
Change { currentState: 0, nextState: 1 }
CounterBloc Change { currentState: 0, nextState: 1 }
bloc에는 addError와 onError 메서드가 있음 / bloc 내부 어디에서나 addError를 호출하여 오류 발생 나타낼 수 있음 => 오류 재정의하여 오류 대응 가능
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) {
addError(Exception('increment error!'), StackTrace.current);
emit(state + 1);
});
}
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
void onTransition(Transition<CounterEvent, int> transition) {
print(transition);
super.onTransition(transition);
}
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
// local onError 먼저 호출 => blocObserver에서 글로벌 onError 호출
// onError 및 onChange는 bloc 및 cubit instance 모두 동일한 방식에서 작동
// eventHandler 내에서 처리되지 않은 예외도 onError로 보고 됌