dart / bloc 2 (bloc)

Sunny·2022년 6월 7일

Bloc

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

bloc


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에 전달하여 초기 상태를 지정!

state Changes

블록은 큐빗의 함수가 아닌 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하면 상태 변경이 발생하지 않음

bloc 사용 (counter block 인스턴스 생성하여 사용 가능 => 예시임)


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 Usage

큐빗과 마찬가지로 블록은 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

사용자 정의 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 }

Error Handling

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로 보고 됌
profile
즐거움을 만드는 사람

0개의 댓글