본문 바로가기

✏️/Flutter

[flutter] BLoC v.6.xx > 7.1.x으로 되면서 달라진 점

728x90

Flutter로 처음 개발 할 때 BLoC 패턴으로 입문해서 그런지, 아무리 불편해도 BLoC 패턴을 자주 사용한다.
(최근에 GetX를 써보니, dependency injection 부분이 정말 신세계였다...😭)

처음 시작 당시에는 flutter version이 1.2xx 이기도 했었고 해서,
이번에는 flutter 2.xx로 업그레이드하고 코딩을 하고 있는데 기존에 사용했던 코드가 안먹히는 ㅎㅎ; 상태가 발생했다.

null-safefy 추가와 함께 BLoC에서는 어떠한 변화가 있는지, 알아본 것을 바탕으로 글을 써본다.


1. BlocBase

bloc 패키지에서 상태를 관리하는 방법으로 Bloc과 Cubit을 제시하고 있습니다.

Cubit은 이벤트의 개념이 없이, state를 가볍게 관리 할 수 있는 개념으로 사용되었는데요,

기존에는 Bloc class와 Cubit class가 각각으로 존재했지만, 이제는 BlocBase라는 상위 클래스를 기반으로해서 extended 하는 형태로 바뀌었습니다.

기존)

abstract class Cubit<State> extends Stream<State> {
 ..
}
abstract class Bloc<Event, State> extends Cubit<State> implements EventSink<Event> {
 ..
}

 

변경 후)

abstract class BlocBase<State> {
}

abstract class Bloc<Event, State> extends BlocBase<State> {
  /// {@macro bloc}
  Bloc(State initialState) : super(initialState) {
    _bindEventsToStates();
  }
}

abstract class Cubit<State> extends BlocBase<State> {
  /// {@macro cubit}
  Cubit(State initialState) : super(initialState);
}

 

이번 업데이트를 통해 Cubit과 Bloc의 쓰임을 명확하게 나눈 듯한 느낌을 줍니다.

기존에는 cubit과 bloc의 차이가 뭔지 몰라서, 그냥 더 직관(?)적인 cubit을 선호했었는데,
Bloc은 event 기반 state 관리 / Cubit은 (module 단위의) state 관리에 적합하다는 것을 알 수 있었습니다. 

 

그리고 BlocBase 클래스의 등장으로, BlocObserver의 interface도 바뀌었으니, flutter 2.점대로 마이그레이션 하는 분들은 확인해보시길 바랍니다.

class BlocObserver {
 void onCreate(BlocBase bloc) {}
 void onEvent(Bloc bloc, Object? event) {}
 void onChange(BlocBase bloc, Change change) {}
 void onTransition(Bloc bloc, Transition transition) {}
 void onError(BlocBase bloc, Object error, StackTrace stackTrace) {}
 void onClose(BlocBase bloc) {}
}

 

2. null-safety 적용

null-safety 적용으로 인해, dependency injection 후 widget 초기화에서 조심(?)해야 할 것이 생겼습니다.
바로, 의존성 주입 받은 변수의 초기화 시점을 명확하게 선언해줘야 한다는 것인데요,

글로 이야기하면 확실하게 와닿지 않으니 아래 코드를 통해 살펴보겠습니다.

class PostsList extends StatefulWidget {
  @override
  _PostsListState createState() => _PostsListState();
}

class _PostsListState extends State<PostsList> {
  final _scrollController = ScrollController();
  PostBloc _postBloc; 
  // 에러 발생!
  // 컴파일러가 변수 사용 가능하다는 것을 보증할 수 없기 때문에 _postBloc을 초기화 해야 함

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
    _postBloc = context.read<PostBloc>();
  }

 

위 코드를 보면, InitState 에서 초기화 했음에도 불구하고, 변수 선언 시 초기화 하지 않을 경우 에러를 뱉습니다.

따라서, 해당 BlocType을 Optional로 하거나 late 연산자를 사용해서 초기화 시점을 늦추는 방법으로 사용해야합니다.

class _PostsListState extends State<PostsList> {
  final _scrollController = ScrollController();
  late PostBloc _postBloc;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
    _postBloc = context.read<PostBloc>();
  }
}

 

3. BlocSelector

BlocBuilder와 유사하지만,  bloc 상태를 일부를 선택하여 변경될 때만 빌드하고, 
bloc 파라미터를 생략한 경우 해당 컨텍스트에서 사용 중인 bloc을 자동으로 잡아서 사용한다고 합니다.

 

BlocSelector<BlocA, BlocAState, SelectedState>(
  selector: (state) {
    // return selected state based on the provided state.
  },
  builder: (context, state) {
    // return widget here based on the selected state.
  },
)

 

아직 사용해본 적이 없어서 잘은 모르겠지만, 무분별한 build를 방지 할 수 있어 퍼포먼스적인 측면에서 도움이 되지 않을까.. 하는 짧은 생각이 듭니다.

 

 

flutter_bloc 관련해서는 이 정도가 주요 업데이트인 것 같습니다.
그 외에 bloc_test 패키지 쪽으로 많은 업데이트가 있었는데요.  관련해서 테스트 쪽을 사용해보고 다음에 포스팅 해보도록 하겠습니다 ✌️

--

참고

https://bloclibrary.dev

https://github.com/felangel/bloc