일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- reactivex
- flutter web
- GetX
- Codemagic
- Android
- Codepush
- copy on write
- appstore
- DevTools
- MVVM
- reject
- BloC
- Xcode
- Swift
- SHIMMER
- fastlane
- struct
- 성능 개선
- Equatable
- FLUTTER
- FocusNode
- delegate
- 무선빌드
- abstact
- TextInputFormatter
- IOS
- PG결제
- error
- shorebird
- 갤럭시폴드
- Today
- Total
뚝딱뚝딱 모바일
[Flutter] Bloc에 대해 알아보자 본문
안녕하세요!
오늘은 상태관리 라이브러리인 Bloc에 대해 알아보겠습니다.
https://pub.dev/packages/flutter_bloc
Bloc은 Flutter Favorite Package에 선정된 라이브러리 중 하나이며, 6000개 이상의 Likes를 받은, Flutter 개발자들은 한 번쯤 듣거나 사용해 본 라이브러리입니다. Bloc 또한 제공해 주는 문서가 세세하고, 여러 사항에 대한 예제까지 있어 이 포스팅은 개념적인 내용에 대해서 간략하게 요약해보려 합니다.
[참고자료 : Bloc 공식 문서]
왜 Bloc을 사용해야 하나?
Bloc을 사용하는 이유에 대해서 Bloc 개발진들은 이렇게 말합니다.
- Simple - 이해하기 쉽고, 다양한 수준의 개발자가 사용할 수 있게 하기 위해
- Powerful - 어플리케이션을 작은 단위의 컴포넌트로 나눠주어 멋지고 복잡한 애플리케이션 개발을 돕기 위해
- Testable - 어플리케이션의 모든 부분을 쉽게 테스트할 수 있게 하여 코드에 확신을 가지고 개발을 진행할 수 있게 위해
Bloc의 주요 개념
위의 주장한 Bloc의 강력한 요소들을 이루는 주요 개념들에 대해 알아보겠습니다.
Bloc을 익히기 전에, Stream에 대해 잘 모르신다면 먼저 공부하고 오시는 것을 추천합니다. Bloc을 사용할 때 주로 쓰이는 요소 중 하나이므로, 사전 지식 없이 이해하기 힘드실 수 있습니다. (이 글에선 Stream에 관한 내용이 나오지 않습니다...!!)
Cubit
Cubit은 Cubit이 관리할 State를 가지고 있고, 상태를 변화시키는 함수들을 가지고 있습니다.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
Cubit의 state들은 primitive 타입 대신 class 또한 사용할 수 있습니다.
그리고 state를 변경시키는 emit 함수는 Cubit 내부에서만 사용할 수 있습니다.
이제 이 Cubit을 UI와 연결해 봅시다.
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (_) => CounterCubit(),
child: Scaffold(
body: BlocBuilder<CounterCubit, int>(
builder: (context, state) {
return Center(
child: GestureDetector(
onTap: () => context.read<CounterCubit>().increment(),
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
child: Center(child: Text(state.toString()))
)
),
);
},
),
),
);
}
}
Cubit을 사용하기 위해 BlocProvider를 위젯 트리 최상단에 감싸주고, 사용할 부분의 위젯에 BlocBuild를 감싸주었습니다.
위 UI는 숫자가 적힌 노란색 Container를 클릭하면 숫자가 1씩 증가하는 매우 간단한 Counter입니다. onTap 함수를 보시면, increment() 함수를 불러와 state를 변경시켜 준다는 것을 알 수 있습니다.
또한, Cubit은 state의 변화를 감지하는 onChange, error를 캐칭 하는 onError를 재정의하여 사용할 수 있습니다.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() {
addError(Exception('increment error!'), StackTrace.current);
emit(state + 1);
}
@override
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
Bloc
Bloc은 함수를 통한 state 변화가 아닌 event에 의존하는 클래스입니다.
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
CounterEvent 클래스를 정의하고, 이를 상속받는 CounterIncrementPressed 클래스도 만들어줍니다.
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) {
emit(state + 1);
});
}
}
CounterBloc은 Bloc<Event, State>를 상속받아 만들어줍니다.
Bloc은 Cubit과 다르게 on<Event>를 사용하여 이벤트 핸들러를 등록하도록 합니다.
위 코드에서는 CounterIncrementPressed가 불리게 되면 state를 1 더해줍니다.
이제 Bloc도 UI와 연결해 줍시다.
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (_) => CounterBloc(),
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, state) {
return Center(
child: GestureDetector(
onTap: () => context.read<CounterBloc>().add(CounterIncrementPressed()),
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
child: Center(child: Text(state.toString()))
)
),
);
},
),
),
);
}
}
Cubit과 구조가 거의 같습니다. 다만 onTap 부분을 보면 미리 정의해 둔 Event인 CounterIncrementPressed 클래스를 Bloc으로 넘겨줌으로써, state를 변경한다는 점이 Bloc과 Cubit의 차이점입니다.
Bloc도 마찬가지로 onChange, onError를 재정의하여 사용할 수 있습니다.
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
@override
void onEvent(CounterEvent event) {
super.onEvent(event);
print(event);
}
@override
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
@override
void onTransition(Transition<CounterEvent, int> transition) {
super.onTransition(transition);
print(transition);
}
}
Cubit과 Bloc, 언제 써야 할까?
위에서도 잠깐 설명했지만, Cubit과 Bloc은 명확한 차이를 보여줍니다. 그럼 이 둘을 어떻게 해야 잘 쓸 수 있을까요? 이 둘의 장점에 대해 알아보고, 분류해 봅시다.
Cubit
Cubit의 큰 장점은 간단하다는 것입니다. Cubit은 state와 state를 변경할 함수만 정의하면 됩니다. Event와 EventHandler까지 구현해야 하는 Bloc과 비교해 보면 쉽게 알 수 있습니다.
Bloc
Bloc은 대신, state 변화뿐만 아니라, 무엇이 변화를 트리거했는지 알 수 있습니다.
Bloc 문서에서는 토큰 인증에 관하여 쉽게 예시를 보여줍니다.
enum으로 알 수 없음, 인증됨, 인증되지 않음 이렇게 3개의 타입을 만들었습니다.
enum AuthenticationState { unknown, authenticated, unauthenticated }
authenticated에서 unauthenticated가 되는 경우에는 여러 가지가 있습니다.
(유저가 로그아웃을 했다, 토큰이 만료되어 로그아웃이 되었다 등)
이럴 때, Bloc을 사용하여, 어떤 이유 때문에 특정 상태로 설정되었는지 알 수 있습니다.
// Bloc을 활용했을 때
Transition {
currentState: AuthenticationState.authenticated,
event: LogoutRequested,
nextState: AuthenticationState.unauthenticated
}
// Cubit을 활용했을 때
Change {
currentState: AuthenticationState.authenticated,
nextState: AuthenticationState.unauthenticated
}
위처럼 출력로그에 event가 같이 나오면서, 쉽게 사유를 파악할 수 있습니다.
또, Bloc은 buffer, debounceTime, throttle 같은 반응형 연산자를 사용할 수 있습니다.
본인이 필요한 것에 따라 커스텀 EventTransformer를 사용하여 이벤트 처리 방식을 제어할 수 있습니다.
EventTransformer<T> debounce<T>(Duration duration) {
return (events, mapper) => events.debounceTime(duration).flatMap(mapper);
}
// CounterBloc
.
.
CounterBloc() : super(0) {
on<Increment>(
(event, emit) => emit(state + 1),
transformer: debounce(const Duration(milliseconds: 300)),
);
}
본인이 판단했을 때, 간단한 처리만 하면 되거나, 함수를 통한 상태 변화가 더 깔끔한 것 같다 싶으면 Cubit,
여러 부가적인 이벤트들이 있고, 이를 처리해야 할 때는 Bloc 이렇게 사용하면 될 것 같습니다.
이렇게 Bloc 라이브러리에 대해 알아보았습니다. Bloc 라이브러리가 러닝커브가 조금 있기에, 이제 막 개발을 시작하거나, Flutter를 처음 익히려는 분들에게는 어려울 수 있습니다. (이상한 게 아닙니다..! 다 그래요 다) 하지만, 상태 관리와 로직을 매우 깔끔하게 처리할 수 있는 라이브러리라고 생각됩니다.
'Flutter 지식' 카테고리의 다른 글
[Flutter] Fastlane으로 배포 자동화를 해보자 (2) (0) | 2023.11.10 |
---|---|
[Flutter] Fastlane으로 배포 자동화를 해보자 (1) (0) | 2023.11.09 |
[Flutter] GetX에 대해 조금만 알아보자 (0) | 2023.10.30 |
[Flutter] Shimmer 라이브러리에 대해 알아보자 (0) | 2023.10.25 |
[Flutter][Error] Try launching Xcode and selecting "Product > Run" to fix the problem (1) | 2023.10.20 |