Business Logic Component

16 September 2019 — Written by Paul Han
#bloc#flutter

BLoC

Business Logic Component

Bloc

왜 BLoC인가요?

Bloc을 사용하면 프레젠테이션을 비즈니스 로직과 쉽게 분리하여 코드를 빠르고 테스트하고 재사용 할 수 있습니다.

생산 품질 애플리케이션을 구축 할 때 상태 관리가 중요해집니다.

개발자로서 우리는 :

  • 어떤 시점에서 우리의 어플리케이션이 어떤 상태인지 알 수 있습니다.
  • 쉽게 모든 사례를 테스트하여 앱이 적절하게 응답하는지 확인하십시오.
  • 데이터 중심 의사 결정을 내릴 수 있도록 애플리케이션에서 모든 단일 사용자 상호 작용을 기록하십시오.
  • 가능한 한 효율적으로 작업하고 응용 프로그램 및 다른 응용 프로그램에서 구성 요소를 재사용하십시오.
  • 동일한 패턴과 규칙에 따라 많은 개발자가 단일 코드 기반 내에서 원활하게 작업 할 수 있습니다.
  • 빠르고 반응적인 앱 개발

Bloc은 이러한 모든 요구를 충족 시키도록 설계되었습니다.

많은 상태 관리 솔루션이 있으며 사용할 솔루션을 결정하는 것은 어려운 작업입니다.

Bloc은 세 가지 핵심 가치를 염두에두고 설계되었습니다.

단순한(Simple)

다양한 기술 수준의 개발자가 이해하기 쉽고 사용할 수 있습니다.

강한(Powerful)

작은 구성 요소로 구성하여 놀랍고 복잡한 응용 프로그램을 만들 수 있습니다.

테스트 가능(Testable)

자신감있게 반복 할 수 있도록 애플리케이션의 모든 측면을 쉽게 테스트하십시오.

Bloc은 상태 변경이 발생할 수 있는 시기를 조정하고 전체 응용 프로그램에서 상태를 변경하는 단일 방법을 시행하여 상태 변경을 예측 가능하게 만듭니다.

핵심 개념들

Bloc 사용 방법을 이해하는 데 중요한 몇 가지 핵심 개념이 있습니다.

다음 섹션에서는 각 응용 프로그램에 대해 자세히 설명하고 실제 응용 프로그램 인 카운터 응용 프로그램에 적용하는 방법을 살펴 봅니다.

Events

이벤트는 Bloc에 입력됩니다. 일반적으로 버튼 누르기 또는 페이지로드와 같은 수명주기 이벤트와 같은 사용자 상호 작용에 따라 전달됩니다.

앱을 디자인 할 때 우리는 물러나서 사용자가 앱과 상호 작용하는 방법을 정의해야합니다.

카운터 앱과 관련하여 카운터를 늘리거나 줄일 수있는 두 개의 버튼이 있습니다.사용자가이 버튼 중 하나를 탭하면 사용자의 입력에 응답 할 수 있도록 앱의 "두뇌"에 무언가를 알려 주어야합니다.

이벤트가 시작되는 곳입니다.애플리케이션의 "두뇌"에 증가와 감소를 모두 알릴 수 있어야하므로 이러한 이벤트를 정의해야합니다.

enum CounterEvent { increment, decrement }

이 경우 enum 형을 사용하여 이벤트를 나타낼 수 있지만 더 복잡한 경우에는 특히 Bloc에 정보를 전달 해야하는 경우 class를 사용해야 할 수도 있습니다.

이제 첫 번째 이벤트를 정의했습니다! 우리는 지금까지 어떤 식 으로 든 Bloc을 사용하지 않았으며 마술이 일어나지 않습니다. 그냥 일반 다트 코드입니다.

States

상태는 Bloc의 출력이며 응용 프로그램 상태의 일부를 나타냅니다. UI 구성 요소는 현재 상태를 기반으로 상태를 알리고 자체 부분을 다시 그릴 수 있습니다.

지금까지 앱이 응답 할 두 가지 이벤트 CounterEvent.incrementCounterEvent.decrement를 정의했습니다.

이제 애플리케이션 상태를 나타내는 방법을 정의해야합니다.

카운터를 작성하기 때문에 상태는 매우 간단합니다. 카운터의 현재 값을 나타내는 정수 일 뿐입니다.

우리는 나중에 더 복잡한 상태의 예를 보게 될 것입니다. 그러나 이 경우 원시타입(정수형)이 상태 표현으로 완벽하게 적합합니다.

Transitions

한 상태에서 다른 상태로 변경하는 것을 Transitions이라고 합니다. Transitions은 현재 상태, 이벤트 및 다음 상태로 구성됩니다.

사용자가 카운터 앱과 상호 작용하면 카운터의 상태를 업데이트하는 IncrementDecrement 이벤트를 트리거합니다. 이러한 모든 상태 변경은 일련의 Transitions으로 설명 할 수 있습니다.

예를 들어 사용자가 앱을 열고 증가 버튼을 한 번 누르면 다음의 Transitions이 표시됩니다.

{
    "currentState": 0,
    "event": "CounterEvent.increment",
    "nextState": 1
}

모든 상태 변경이 기록되므로 애플리케이션을 매우 쉽게 계측하고 한 곳에서 모든 사용자 상호 작용 및 상태 변경을 추적 할 수 있습니다. 또한 시간 이동 디버깅과 같은 기능이 가능합니다.

Blocs

Bloc (Business Logic Component)은 들어오는 EventsStream을 나가는 StatesStream으로 변환하는 구성 요소입니다. Bloc을 위에서 설명한 "두뇌"라고 생각하십시오.

모든 Bloc은 핵심 Bloc 패키지의 일부인 기본 Bloc Class를 확장해야 합니다.

import 'package:bloc/bloc.dart';

class CounterBloc extends Bloc<CounterEvent, int> {

}

위 코드 스니펫에서 CounterBlocCounterEventint로 변환하는 Bloc으로 선언되었 습니다.

모든 Bloc은 이벤트가 수신되기 전의 상태 인 초기 상태를 정의해야합니다.

이 경우 카운트가 0에서 시작하기를 원합니다.


int get initialState => 0;

모든 Bloc은 mapEventToState라는 함수를 구현해야합니다. 이 함수는 들어오는 event를 인수로 취하여 프레젠테이션 계층에서 사용하는 새로운 states Stream을 반환해야합니다. currentState 속성을 사용하여 언제든지 현재 Bloc 상태에 액세스 할 수 있습니다.


Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
        case CounterEvent.decrement:
        yield currentState - 1;
        break;
        case CounterEvent.increment:
        yield currentState + 1;
        break;
    }
}

이제 완전히 작동하는 CounterBloc이 있습니다.

import 'package:bloc/bloc.dart';

enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
    
    int get initialState => 0;

    
    Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
        case CounterEvent.decrement:
        yield currentState - 1;
        break;
        case CounterEvent.increment:
        yield currentState + 1;
        break;
    }
    }
}

Bloc은 중복 상태를 무시합니다. Bloc이 currentState == stateState state 를 생성(yield)하면 전환이 발생하지 않으며 Stream<State> 는 변경되지 않습니다.

이 시점에서 "Event를 Bloc에 알리려면 어떻게 해야 합니까?"

모든 Bloc에는 dispatch 메소드가 있습니다. Dispatchevent를 가져와 mapEventToState를 트리거합니다. Dispatch는 프리젠 테이션 레이어 또는 Bloc 내에서 호출 될 수 있으며 새로운 event를 Bloc에 알립니다.

0에서 3까지 카운트 하는 간단한 응용 프로그램을 만들 수 있습니다.

void main() {
    CounterBloc bloc = CounterBloc();

    for (int i = 0; i < 3; i++) {
        bloc.dispatch(CounterEvent.increment);
    }
}

위 코드 스니펫의 Transitions

{
    "currentState": 0,
    "event": "CounterEvent.increment",
    "nextState": 1
}
{
    "currentState": 1,
    "event": "CounterEvent.increment",
    "nextState": 2
}
{
    "currentState": 2,
    "event": "CounterEvent.increment",
    "nextState": 3
}

불행하게도 현재 상태에서는 onTransition을 재정의하지 않으면 이러한 전환을 볼 수 없습니다.

onTransition은 모든 로컬 Bloc Transition을 처리하기 위해 재정의 할 수있는 방법입니다. Bloc state가 업데이트되기 직전에 onTransition이 호출됩니다.

Tip : onTransition은 bloc-specific logging/analytics을 추가하기에 좋은 장소입니다.


void onTransition(Transition<CounterEvent, int> transition) {
    print(transition);
}

이제 onTansition을 재정의 했으므로 Transition이 발생할 때마다 원하는 모든 작업을 수행 할 수 있습니다.

블록 수준에서 Transitions을 처리 할 수있는 것처럼 Exceptions도 처리 할 수 ​​있습니다.

onError는 모든 로컬 블록 Exceptions를 처리하기 위해 재정의 할 수있는 메소드입니다. 기본적으로 모든 예외는 무시되고 Bloc 기능은 영향을 받지 않습니다.

Note: 상태 스트림이 StackTrace없이 오류를 수신 한 경우 stacktrace 인수는 null 일 수 있습니다.

Tip : onError는 블록 별 오류 처리를 추가하기에 좋은 장소입니다.


void onError(Object error, StackTrace stackTrace) {
    print('$error, $stackTrace');
}

이제 onError를 재정의 했으므로 Exception이 발생할 때마다 원하는 모든 작업을 수행 할 수 있습니다.

BlocDelegate

Bloc 사용의 한 가지 추가 보너스는 한 곳에서 모든 Transitions에 액세스 할 수 있다는 것입니다. 이 애플리케이션에는 Bloc이 하나만 있지만 더 큰 애플리케이션에서는 많은 Bloc이 애플리케이션 상태의 다른 부분을 관리하는 것이 일반적입니다.

모든 Transitions에 대한 응답으로 무언가를하고 싶다면 BlocDelegate를 만들면됩니다.

class SimpleBlocDelegate extends BlocDelegate {
    
    void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
    }
}

Note: BlocDelegate를 확장하고 onTransition 메소드를 재정의 하기만 하면 됩니다.

Bloc에게 SimpleBlocDelegate를 사용하도록하려면, 우리의 main 함수를 조정하면됩니다.

void main() {
    BlocSupervisor.delegate = SimpleBlocDelegate();
    CounterBloc bloc = CounterBloc();

    for (int i = 0; i < 3; i++) {
    bloc.dispatch(CounterEvent.increment);
    }
}

전달 된 모든 Event에 대한 응답으로 무언가를 수행하려는 경우 SimpleBlocDelegateonEvent 메소드를 재정의 할 수도 있습니다.

class SimpleBlocDelegate extends BlocDelegate {
    
    void onEvent(Bloc bloc, Object event) {
    super.onEvent(bloc, event);
    print(event);
    }

    
    void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
    }
}

Bloc에서 발생하는 모든 Exceptions에 대한 응답으로 무언가를 수행하려면 SimpleBlocDelegate에서 onError 메소드를 재정의 할 수도 있습니다.

class SimpleBlocDelegate extends BlocDelegate {
    
    void onEvent(Bloc bloc, Object event) {
    super.onEvent(bloc, event);
    print(event);
    }

    
    void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
    }

    
    void onError(Bloc bloc, Object error, StackTrace stacktrace) {
    super.onError(bloc, error, stacktrace);
    print('$error, $stacktrace');
    }
}

Note: BlocSupervisor는 모든 Bloc을 감독하고 BlocDelegate에 책임을 위임하는 싱글 톤입니다.