Flutter 디자인 패턴 (bloc vs provider)

김규빈·2022년 2월 18일
5
post-thumbnail

플러터에서 자주 쓰는 두 가지의 디자인 패턴에 대해 아라보자

인트로

어니언은 flutter getX package를 이용하여 MVVM패턴으로 빌드가 되었는데 view, viewModel(controller)사이를 분리하여 stateful, stateless 의 경계를 없애고 각 view별로 controller를 이용하여 상태관리를 하고 있다. view에서 분리 되어진 viewModel에서는 data layer 계층과 상호작용하며 remote 데이터 (api), local 데이터(storage)를 view에게 전달해주는 역할을 한다. 따라서 UI의 이슈는 view, 비지니스 로직의 이슈는 viewModel, 데이터 송수신의 이슈는 data layer에게만 상관 관계가 있기 때문에 레이어별로 역할을 명확히 할 수 있고, data와 action의 흐름을 일관되게 할 때에만 확장성 있고, 이해하기 쉽우며, 테스트하기 좋은 앱으로 만들 수 있다.

오늘의 정리 할 내용은 앱 프로젝트의 초석인 디자인 패턴에 관한 내용이다.

bloc 패턴

bloc 라는 패턴은 플러터를 스터디 하다 처음 들어본 디자인 패턴이다. 그래서 알아보니 dartconf에서 구글 개발자가 고안한 플러터를 위한 디자인 패턴이라고 한다. 이름은 무섭게 생겼지만 결국엔 뷰에서의 비즈니스 로직을 어떻게 하면 분리 할 수 있을까에 대한 패턴이다. provider패턴도 목적은 같지만 분리된 로직에서 뷰까지 상태에 흐름의 방법에 따라 달라진 명칭이다.

BLoC Pattern 이란 Bussiness Logic Component의 줄임말이다.
bloc 패턴은 stream을 통해 상태를 업데이트 하는데, 이것을 통해 flutter에서는 stateful,stateless나 setState()가 필요 없어진다.
stream에 관한 재밌는 설명
bloc 패턴은 리덕스나 뷰엑스의 개념과 매우 유사한데,

BLoC 에서 각 UI 객체 들은 BLoC 객체를 구독하고 있다.

BLoC 객체의 상태가 변경되면, BLoC 의 상태를 구독중인 UI 객체 들은 그 즉시 해당 상태로 UI 를 변경한다.

BLoC 객체는 UI 객체로 부터 이벤트를 전달받으면, BLoC 객체는 필요한 Provider 나 Repository 로 부터 데이터를 전달받아, Bussiness Logic 을 처리한다.

Bussiness Logic 을 처리한후, BLoC 객체를 구독중인 UI 객체 들에게 상태를 전달한다.

class Bloc {
  final _repository = Repository();
  final _subject = PublishSubject<String>();
  Observable<String> get stream => _subject.stream;
  action() async {
    String result= await _repository.getData();
    _subject.sink.add(result);
  }
  dispose() {
    _subject.close();
  }
}

BLoC 은 Sink 라는 진입점을 가지고, Bussiness Logic 을 처리하고, 새로운 상태를 만들어, Steam 을 구독하고 있는 UI 에게 전달한다. 덕분에 setState()없이 ui업데이트가 가능 해진다.

뷰엑스와 리덕스는 dispatch의 호출을 통해 상태가 변경된다면 bloc는 stream을 통해 호출이 없더라도 상태가 변경된다.

BLoC 의 특징
UI 에서는 여러 BLoC 이 존재할 수 있다.
UI 에서는 화면에 집중하고, BLoC 에서는 Logic 에 집중한다.
UI 에서는 BLoC 의 내부 구현에 대해서 몰라도 된다.
BLoC 은 여러 UI 에서 구독 할 수 있다. 때문에 재사용이 용의하다.
BLoC 만을 분리해서 테스트가 가능하다.

provider

provider 패턴은 공통 부모 위젯에 Provider를 주입 하고, 상태를 사용하는 곳에는 Provider의 데이터를 읽어서 사용하게 된다.

provider의 설명을 듣다보면 대부분 Provider는 데이터를 생산하고, 소비하는 객체 라고 하는데,
그냥 쉽게 말해 전역(루트에 주입 되어진)에 데이터를 넣고, 빼와서 사용하는 객체이다. 모든 위젯 트리에서 글로벌 객체에 접근이 가능하게 되면서 햄버거 상속 같은 이상한 구조를 피할 수 있게된다.

부모에게 provider를 주입하기 때문에 context를 통해 접근하고, 데이터를 가져올때 방식에 따라 그냥 가져오거나 반응형 형식의 streamProvider를 통해 가져오게 된다.

개념은 bloc보다 훨씬 쉽고 간결하지만

return Center(
      child: Text(
        Provider.of<CounterProvider>(context).count.toString(),
      ),
    );

이와같이 provider에 직접 접근해서 받아오게 될 경우 위젯 전체가 업데이트 되기 때문에

return Center(
      child: Consumer<CounterProvider>(builder: (context, provider, child) {
        return Text(
          provider.count.toString(),
        );
      }),
    );

Consumer를 활용하여 builder 부분만 호출하여 부분 업데이트를 하는 방식으로 나눠서 사용할 것.

여러 효과나 연산이 많이 있는 위젯일 경우는 Consumer를 사용하는 것이 좋다.

profile
FrontEnd Developer

1개의 댓글

comment-user-thumbnail
2022년 8월 29일

군더더기 없이 간결하게 잘 작성해주셔서 큰 도움이 되었습니다.
감사합니다.

답글 달기