[Flutter] BLoC(Business Logic Component) 패턴
BLoC 패턴을 학습하기 위하여 유튜버 "개발하는남자"님의 컨텐츠를 정리한 내용입니다. 내용이 너무 좋아서 시청해보시기를 추천드립니다.
https://www.youtube.com/watch?v=txqDpaERR1s&list=PLgRxBCVPaZ_0ZJevIcngLXMrm-1RPXxRW&index=2
개요
BLoC란?
프레젠테이션에서 비즈니스 로직을 분리하고 Flutter 애플리케이션의 상태 관리를 처리하는 상태 관리 아키텍처다. 스트림 및 반응형 프로그래밍을 기반으로하며 상태를 작고 관리 가능한 청크로 분해하여 복잡한 상태를 관리하는 방법을 제공한다.
BLoC을 사용해야하는 이유
BLoC은 큰 프로젝트에 적합한 상태 관리 패턴이다.
1. 뷰 영역과 비지니스 영역을 쉽게 구분할 수 있다.
뷰 영역(화면 UI) : 사용자의 Action(검색, 입력)을 받아서 Event를 발생시키는 역할을 함
비지니스 : 사용자의 입력(요청)에 따라서 여러 기능(회원가입, 검색, 구매 등)을 수행
뷰와 비지니스가 서로 상호작용을 하여 서비스가 됨
2. 테스트하기 쉽고 재사용을 가능하게 해준다.
BLoC test를 제공하고 있어서 TDD를 중점적으로 활용하는 개발자에게 장점이 있다. 직접 개발자가 여러 입력과 예외상황을 고려해서 테스트하고 버그를 찾아 수정해야하는데 시간이 많이 걸리거나 구멍이 발생하게된다.
TDD는 여러시나리오에 대한 기댓값을 설정해서 TDD를 통과하기만하면 검증된 코드라고 간주하고 사용할 수 있는데 BLoC test는 이에 최적화되어있다.
3. 이벤트 트레킹을 통합적으로 관리할 수 있다.
BLoC에서는 BlocObserver를 통해서 이벤트를 통합적으로 관리할 수 있다.
GetX에서는 지원하지 못하는 기능이다.
4. 많은 개발자들의 (하나의)코드 베이스로 일을 처리할 수 있다.
BLoC은 규칙이 정해져있기 때문에 개발 규칙을 따르게 되어 업무 효율이 생긴다.
5. Github에서 가장 높은 Star수를 가지고 있다.
BLoC 패키지로 입문하자
직접 BLoC 패턴을 작성하려면 많은 라인의 코드를 작성해야하지만 flutter_bloc 패키지를 사용하면 입문하기에 장벽이 낮아진다.
핵심요소
Stream
BLoC는 스트림을 사용하여 상태 변경을 처리하고 UI 업데이트를 트리거합니다. 스트림은 위젯이나 다른 구성 요소에서 수신하고 응답할 수 있는 일련의 비동기 이벤트입니다.
Stream을 알기전에 Future에 대해서
비동기적인 작업을 수행할때 사용
API 통신을 통해 데이터를 얻을때 사용
Stream의 사전적 의미 : 연속, 흐르다
Dart에서 Stream은 데이터들을 흘려보내서 순차적으로 처리하는 방법
Future가 여러개가 있는 것으로 생각해볼 수 있음
Sink
싱크는 스트림에 이벤트를 추가하는 방법입니다. BLoC에서 싱크는 상태 변경을 트리거하는 스트림에 이벤트를 보내는 데 사용됩니다.
StreamController
스트림 컨트롤러는 스트림을 만들고 이벤트를 관리하는 방법입니다. BLoC에서 스트림 컨트롤러는 스트림을 만들고 관리하는 데 사용됩니다.
BlocProvider
BlocProvider는 자손에게 BLoC 인스턴스를 제공하는 위젯입니다. 이는 다른 상태 관리 아키텍처의 공급자 위젯과 유사합니다.
Bloc widget 종류
Bloc widget은 flutter_bloc 패키지에서 사용할 수 있다. 먼저 필수적으로 사용하는 위젯을 살펴본다.
BlocProvider
Bloc이나 Cubit을 사용하기 위해서는 context에 등록해야 사용이 가능하다. 그것을 위해 사용한다.
하위계층 위젯들에서 접근 가능
Bloc 생성 후 메모리 반환을 자동으로 처리
lazy : 지연생성 옵션
return BlocProvider( // Bloc을 제공하는 위젯
create: (context) => SampleBloc(), //Bloc을 사용하기위해 Bloc class를 context에 등록
lazy: false, // default: true. Bloc을 어느 시점에 생성할지 지정. 생성과 동시에 사용하려면 false.
child: SamplePage(), // 생성하고 등록한 Bloc에 영향을 받을 영역(자식) 위젯을 등록
);
MultiBlocProvider
BlocProvider 다중 등록
여러개의 Bloc에 영향을 받을 자식 widget을 등록할 수 있음
BlocProvider를 여러번 사용하는 것과 동일한 기능을 수행하지만 가독성을 위해 사용하는 것을 추천한다.
BlocBuilder
BlocProvider로 생성된 bloc을 사용할때 쓰는 Widget
bloc 옵션 미지정: 현 context로부터 bloc을 찾아서 변화를 감지한다.
bloc 옵션 지정: 특별한 케이스에서 사용하라고 권장함. 현재의 buildcontext를 통해 bloc에 접근할 수 없는 위젯트리에서 사용. (showDialog 를 사용할때). chatGPT에 물어보니 여러 bloc이 있고 특정 bloc의 상태가 변경될 때만 UI를 업데이트하려는 경우에 유용하게 사용할 수 있다고 함
buildWhen 옵션을 통해 필요한 조건일때만 변화를 줄 수 있다.
RepositoryProvider
Bloc이 아닌 일반 class를 하위 widget에서 context로 쉽게 접근할 수 있게 하는 Widget이다.
단, Repository의 목적에 맞게 사용해야한다. (local data 관리, database처리, 외부 API 통신을 통해서 데이터 관리)
즉, 저장소 데이터를 가공할 수 있는 데이터베이스 or 외부 API 통신 등 관리할때 사용하는 것이다.
지연생성 옵션(lazy)을 통해 관리할 수 있다.
MultiRepositoryProvider
가독성을 좋게하기 위한 widget으로 보면 된다.
다음으로는 알아두면 좋은 위젯들을 알아본다.
BlocSelector
Bloc의 상태 중 필요한 부분만 선택적으로 필터링하여 변경에 도움을 주는 widget
BlocBuilder는 상태 요소 중 하나라도 변경되었을때 모두 build하지만 BlocSelector는 selector에 지정한 상태 요소가 변경될 때만 build할 수 있다
BlocBuilder의 buildwhen은 조건이고 BlocSelector의 selector는 필터라고 이해하면 된다.
BlocListener, MultiBlocListener
상태변화에 따른 이벤트만 처리가 필요할때 사용되는 widget
Cubit과 type을 제네릭으로 선언해주어야한다.
*child 위젯의 경우 rebuild가 발생되지 않는다.
특정 상태가 변경되었을때 메세지 팝업을 띄워야하는 상황, Bloc 간 통신이 필요할때 사용한다
BlocConsumer
BlocBuilder와 BlocListener를 합쳐놓은 위젯
이벤트도 처리하면서 동시에 화면도 변경해줘야 할때 사용
buildWhen과 listenWhen 조건을 통해 적절한 때에만 변경 및 이벤트처리를 할 수 있다.
Bloc/Cubit 상태 관리
Cubit 구조
Cubit은 GetX의 controller와 비슷한 역할을 한다. 차이점이라면 GetX에서는 하나의 controller에 여러가지 상태변수를 관리할 수 있는데 하나의 Cubit에는 하나의 상태만 관리할 수 있다.
Cubit을 상속받고 관리할 상태의 type을 지정해준다. 그리고 지정한 type의 상태 초기값을 지정해준다. 부모 class에서 generic type으로 지정해두었던 상태 값을 state라는 변수로 사용하게된다. Cubit안의 함수는 상태를 변경하는 로직을 처리한다. emit 함수에 담아서 state에 적용하며, emit은 Cubit이나 Bloc 내에서만 사용할 수 있다.
CountCubit class에서는 상태를 변경하는 두 함수와 하나의 상태 state를 관리하고, View에서 BlocBuilder의 builder가 호출되어 CountCubit의 state 값을 화면에 보여주거나 context.read<CountCubit>().addCount 함수를 호출하여 state를 변경하게 된다.
Cubit을 사용하더라도 Class에 여러 상태를 등록하고 copyWith를 통해서 하나의 Cubit에서 관리할 수 있는 개발도 가능하다. 이렇게 하면 GetX처럼 하나의 Cubit(GetX라면 controller)에서 여러가지 상태를 관리하는 개발 방법을 사용할 수 있다.
Bloc 구조
Cubit이 Cubit과 State로 구성되어있다면 Bloc은 Event가 추가된다. 코드의 양이 많아지지만 Event에 대한 tracking이 가능한 장점이 있다.
Bloc Class를 생성할때 Bloc을 상속받고 EventClass와 StateClass 두가지 generic type을 지정해준다. 이 예시에서는 CountEvent를 abstract class로 생성하고 AddCountEvent와 SubstractCountEvent가 각 각 CountEvent를 상속함으로서 CountEvent를 EventClass로 등록할 수 있다.
Bloc class에서는 "on"을 통해 이벤트를 구독한다. View에서 Event가 등록될때 AddCountEvent와 SubstractCountEvent 중 어떤 Event에 접근할 것인지 지정해서 해당 Event 함수를 실행할 수 있다.
Bloc과 Cubit 비교
Bloc만 있는 기능
(좌)Cubit으로 이벤트 처리하는 경우, onChange로 event 발생한 경우, 그 결과를 모니터링 할 수 있다.
(우)Bloc은 onChange, onTransition을 통해서 어떤 함수를 통해 event가 실행되었고 그 결과가 어떻게 됬는지 추가적인 정보를 확인하게 됨으로써 event에 대해 detail한 tracking이 가능하게 된다. 디버그나 유지보수시에 로그로 디테일한 내용을 볼 수 있는 것은 Bloc만 가능하다.
transformer로 일정 시간동안 대기했다가 event를 처리할 수 있는 기능이 있다.
여기에 추가적으로 bloc_concurrency라는 기능을 사용하면 사용자의 요청에 대해서 처리 방식을 다양하게 조절할 수 있다.
concurrent : bloc의 기본 처리방식
sequential : 동기 방식 이벤트가 종료되면 다음 이벤트 수행
restartable : 최근 이벤트만 수행하고 이전 이벤트는 제거
droppable : 이전 이벤트가 처리되는 동안 들어오는 이벤트 제거
Reference
https://bloclibrary.dev/#/whybloc
Bloc State Management Library
Official documentation for the bloc state management library. Support for Dart, Flutter, and AngularDart. Includes examples and tutorials.
bloclibrary.dev
https://pub.dev/packages/flutter_bloc
flutter_bloc | Flutter Package
Flutter Widgets that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package.
pub.dev
https://www.youtube.com/watch?v=txqDpaERR1s&list=PLgRxBCVPaZ_0ZJevIcngLXMrm-1RPXxRW&index=2