Flutter BLoC Core Concepts

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

Flutter Bloc Core Concepts

Flutter Bloc 핵심 개념

flutter_bloc을 사용하기 전에 다음 섹션을 주의 깊게 읽고 이해하십시오.

Bloc Widgets

BlocBuilder

BlocBuilder는 Blocbuilder 기능이 필요한 Flutter widget 입니다. BlocBuilder는 새로운 상태에 대한 응답으로 위젯 빌드를 처리합니다. BlocBuilderStreamBuilder와 매우 유사하지만 필요한 상용구 코드의 양을 줄이기 위해 더 간단한 API가 있습니다. builder 함수는 여러 번 호출 될 수 있으며 상태에 대한 응답으로 위젯을 리턴하는 순수한 함수 여야합니다.

탐색, 대화 상자 표시 ... 등과 같은 상태 변경에 대한 응답으로 "실행"하려는 경우 BlocListener를 참조하십시오.

bloc 매개 변수를 생략하면 BlocBuilderBlocProvider와 현재 BuildContext를 사용하여 자동으로 조회를 수행합니다.

BlocBuilder<BlocA, BlocAState>(
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

단일 위젯으로 범위가 지정되고 상위 BlocProvider 및 현재 BuildContext를 통해 액세스 할 수 없는 블록을 제공하려는 경우에만 블록을 지정하십시오.

BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // provide the local bloc instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

빌더 함수가 호출 될 때 세분화 된 제어를 원하면 BlocBuilder에 선택적 조건을 제공 할 수 있습니다. 조건은 이전 블록 상태와 현재 블록 상태를 가져 와서 boolean을 리턴합니다. condition이 true를 리턴하면 currentState와 함께 builder가 호출되고 위젯이 다시 작성됩니다. condition이 false를 리턴하면 currentStatebuilder가 호출되지 않으며 재 빌드가 발생하지 않습니다.

BlocBuilder<BlocA, BlocAState>(
  condition: (previousState, currentState) {
    // return true/false to determine whether or not
    // to rebuild the widget with currentState
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

BlocProvider

BlocProviderBlocProvider.of (context)를 통해 자식에게 블록을 제공하는 Flutter 위젯입니다. 블록의 단일 인스턴스가 서브 트리 내의 여러 위젯에 제공 될 수 있도록 의존성 주입 (DI) 위젯으로 사용됩니다.

대부분의 경우 BlocProvider를 사용하여 나머지 하위 트리에서 사용할 수있는 새 blocs을 빌드해야합니다. 이 경우 BlocProvider는 블록 생성을 담당하므로 블록 배치를 자동으로 처리합니다.

BlocProvider(
  builder: (BuildContext context) => BlocA(),
  child: ChildA(),
);

경우에 따라 BlocProvider를 사용하여 위젯 블록의 새 부분에 기존 블록을 제공 할 수 있습니다. 기존 bloc을 새 경로에서 사용할 수 있어야 할 때 가장 일반적으로 사용됩니다. 이 경우 BlocProvider는 블록을 생성하지 않았기 때문에 자동으로 블록을 폐기하지 않습니다.

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
  child: ScreenA(),
);

ChildA 또는 ScreenA에서 다음을 사용하여 BlocA를 검색 할 수 있습니다.

BlocProvider.of<BlocA>(context)

MultiBlocProvider

MultiBlocProvider는 여러 BlocProvider 위젯을 하나로 병합하는 Flutter 위젯입니다. MultiBlocProvider는 가독성을 향상시키고 여러 BlocProvider를 중첩시킬 필요가 없습니다. MultiBlocProvider를 사용하여 다음을 수행 할 수 있습니다.

BlocProvider<BlocA>(
  builder: (BuildContext context) => BlocA(),
  child: BlocProvider<BlocB>(
    builder: (BuildContext context) => BlocB(),
    child: BlocProvider<BlocC>(
      builder: (BuildContext context) => BlocC(),
      child: ChildA(),
    )
  )
)

to:

MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      builder: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      builder: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      builder: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)

BlocListener

BlocListenerBlocWidgetListener와 선택적 Bloc을 가져와 블록의 상태 변경에 대한 응답으로 listener를 호출하는 Flutter 위젯입니다. 탐색, SnackBar 표시, Dialog 표시 ... 등과 같이 상태 변경마다 한 번씩 발생해야하는 기능에 사용해야합니다.

listenerBlocBuilderbuilder와 달리 각 상태 변경 (initialState를 포함하지 않음)에 대해 한 번만 호출되며 void 함수입니다.

bloc 매개 변수를 생략하면 BlocListenerBlocProvider와 현재 BuildContext를 사용하여 자동으로 조회를 수행합니다.

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

BlocProvider 및 현재 BuildContext를 통해 액세스 할 수 없는 블록을 제공하려는 경우에만 블록을 지정하십시오.

BlocListener<BlocA, BlocAState>(
  bloc: blocA,
  listener: (context, state) {
    // do stuff here based on BlocA's state
  }
)

리스너 함수가 호출 될 때 세밀하게 제어하려면 BlocListener에 선택적 condition을 제공 할 수 있습니다. condition은 이전 블록 상태와 현재 블록 상태를 가져 와서 부울을 리턴합니다. condition이 true를 반환하면 currentState와 함께 리스너가 호출됩니다. condition이 false를 반환하면 currentState로 리스너가 호출되지 않습니다.

BlocListener<BlocA, BlocAState>(
  condition: (previousState, currentState) {
    // return true/false to determine whether or not
    // to call listener with currentState
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  }
  child: Container(),
)

MultiBlocListener

MultiBlocListener는 여러 BlocListener 위젯을 하나로 병합하는 Flutter 위젯입니다. MultiBlocListener는 가독성을 향상시키고 여러 BlocListener를 중첩시킬 필요가 없습니다. MultiBlocListener를 사용하여 다음을 수행 할 수 있습니다.

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {},
  child: BlocListener<BlocB, BlocBState>(
    listener: (context, state) {},
    child: BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
      child: ChildA(),
    ),
  ),
)

to:

MultiBlocListener(
  listeners: [
    BlocListener<BlocA, BlocAState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocB, BlocBState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
    ),
  ],
  child: ChildA(),
)

RepositoryProvider

RepositoryProviderRepositoryProvider.of(context)를 통해 자식에게 리포지토리를 제공하는 Flutter 위젯입니다. 서브 디렉토리의 단일 위젯을 여러 위젯에 제공 할 수 있도록 종속성 주입 (DI) 위젯으로 사용됩니다. BlocProvider는 블록을 제공하는 데 사용해야하지만 RepositoryProvider는 리포지토리에만 사용해야합니다.

RepositoryProvider(
  builder: (context) => RepositoryA(),
  child: ChildA(),
);

그런 다음 ChildA에서 다음을 사용하여 Repository 인스턴스를 검색 할 수 있습니다.

RepositoryProvider.of<RepositoryA>(context)

MultiRepositoryProvider

MultiRepositoryProvider는 여러 RepositoryProvider 위젯을 하나로 병합하는 Flutter 위젯입니다. MultiRepositoryProvider는 가독성을 향상시키고 여러 RepositoryProvider를 중첩시킬 필요가 없습니다. MultiRepositoryProvider를 사용하여 다음을 수행 할 수 있습니다.

RepositoryProvider<RepositoryA>(
  builder: (context) => RepositoryA(),
  child: RepositoryProvider<RepositoryB>(
    builder: (context) => RepositoryB(),
    child: RepositoryProvider<RepositoryC>(
      builder: (context) => RepositoryC(),
      child: ChildA(),
    )
  )
)

to:

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      builder: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      builder: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      builder: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)

Usage

BlocBuilder를 사용하여 CounterPage 위젯을 CounterBloc에 연결하는 방법을 살펴 보겠습니다.

counter_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;
    }
  }
}

counter_page.dart

class CounterPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterBloc, int>(
        builder: (context, count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                counterBloc.dispatch(CounterEvent.increment);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () {
                counterBloc.dispatch(CounterEvent.decrement);
              },
            ),
          ),
        ],
      ),
    );
  }
}

이 시점에서 프레젠테이션 계층과 비즈니스 논리 계층을 성공적으로 분리했습니다. CounterPage 위젯은 사용자가 버튼을 누를 때 어떤 일이 발생하는지 전혀 알지 못합니다. 위젯은 단순히 CounterBloc에게 사용자가 증가 또는 감소 버튼을 눌렀음을 알려줍니다.