민프
[Flutter] BLoC - 2. 카운터 앱에 간단하게 적용해보기 (with Cubit) 본문
이번 포스팅은 BLoC 패턴을 적용한 카운터 앱에 간단하게 적용해보겠습니다.
참고 예제는 공식홈페이지 예제를 참고하였습니다.
https://bloclibrary.dev/#/fluttercountertutorial
해당 예제에서는 Cubit이라는 개념이 등장하는데 간단하게 BLoC와 장단점을 비교해보겠습니다.
Cubit의 장점
- 간결성: Cubit은 간단하게 상태와 상태를 변경하는 함수만 정의하면 됩니다. 반면 Bloc은 상태, 이벤트, 이벤트 핸들러 구현을 모두 정의해야 합니다. 따라서 Cubit은 이해하기 쉽고 코드가 더 간결합니다.
- 직관성: Cubit에서는 이벤트를 별도로 정의할 필요 없이 함수가 이벤트처럼 작동합니다. 상태 변경을 트리거하려면 Cubit 내의 어디에서나 emit을 호출하기만 하면 됩니다.
Bloc의 장점
- 추적 가능성: Bloc의 주요 장점 중 하나는 상태 변경의 순서와 그러한 변경을 유발한 정확한 원인을 알 수 있다는 것입니다. 애플리케이션의 중요한 상태에 대해 이벤트 기반 접근 방식을 사용하면 모든 이벤트와 상태 변경을 캡처할 수 있습니다.
- 고급 이벤트 변환: Bloc은 buffer, debounceTime, throttle 등의 반응형 연산자를 활용할 필요가 있을 때 Cubit보다 뛰어납니다. Bloc은 이벤트 싱크를 가지고 있어 이벤트의 유입을 제어하고 변환할 수 있습니다.
프로젝트 구조
├── lib
│ ├── app.dart : 앱 그자체
│ ├── counter : 카운터 기능으로 카테고리 구분
│ │ ├── counter.dart : Counter Feature의 모든 부분을 공개함으로써
기능(cubit), UI(view,page)에 관련 된 내용을 export
│ │ ├── cubit
│ │ │ └── counter_cubit.dart : Counter 기능 정의
│ │ └── view
│ │ ├── counter_page.dart : CounterCubit 인스턴스를 생성하고 이를 CounterView에 제공하는 역할을 합니다.
│ │ ├── counter_view.dart : UI 렌더링
│ │ └── view.dart : counter View에 대한 모든 부분을 공개함으로써 UI(view, page)를 export
│ ├── counter_observer.dart : 앱의 상태 변화를 실시간으로 모니터링을 할 수 있음
│ └── main.dart : Root 위젯 설정 및 CounterObserver 설정
├── pubspec.lock
├── pubspec.yaml
Flutter Pagekage 추가
pubspec.yaml
--------------
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.3
bloc: ^8.1.0
소스코드
- BLoC Observer : lib/counter_observer.dart
counter_observer는 애플리케이션의 모든 상태 변화를 관찰하는 역할을 합니다.
import 'package:bloc/bloc.dart';
/// {@template counter_observer}
/// [BlocObserver] for the counter application which
/// observes all state changes.
/// {@endtemplate}
// CounterObserver 클래스로써
// BlocObserver를 확장하여 애플리케이션의 모든 상태 변경을 관찰합니다.
class CounterObserver extends BlocObserver {
/// {@macro counter_observer}
const CounterObserver();
// BlocBase의 상태 변경을 관찰하는 메서드입니다. 상태가 변경될 때마다 이 메서드가 호출됩니다.
@override
void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {
super.onChange(bloc, change);
print('${bloc.runtimeType} $change');
}
}
- main.dart
import 'package:bloc/bloc.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_counter/app.dart';
import 'package:flutter_counter/counter_observer.dart';
void main() {
Bloc.observer = const CounterObserver();
runApp(const CounterApp());
}
Flutter 앱의 진입점인 main.dart 파일의 내용을 설정하는 부분입니다.
여기서는 CounterObserver를 초기화하고 CounterApp 위젯을 실행하여 앱을 시작합니다.
- CounterApp: lib/app.dart
CounterApp이 될 것이며 을 (를) MaterialApp지정하고 있습니다 .
import 'package:flutter/material.dart';
import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_app}
/// A [MaterialApp] which sets the `home` to [CounterPage].
/// {@endtemplate}
class CounterApp extends MaterialApp {
/// {@macro counter_app}
const CounterApp({super.key}) : super(home: const CounterPage());
}
- CounterPage : lib/counter/view/counter_page.dart
CounterPage 위젯을 정의하는 부분입니다.
CounterPage는 StatelessWidget을 확장하며,
주로 CounterCubit 인스턴스를 생성하고 이를 CounterView에 제공하는 역할을 합니다.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_page}
/// A [StatelessWidget] which is responsible for providing a
/// [CounterCubit] instance to the [CounterView].
/// {@endtemplate}
class CounterPage extends StatelessWidget {
/// {@macro counter_page}
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: const CounterView(),
);
}
}
- Counter Cubit : lib/counter/cubit/counter_cubit.dart
CounterCubit 클래스는 앱의 상태를 관리하는 로직을 포함하고 있으며,
increment와 decrement 메서드를 통해 상태를 변경할 수 있습니다.
이러한 상태 변경은 CounterView와 같은 위젯에서 UI를 업데이트하는 데 사용됩니다.
import 'package:bloc/bloc.dart';
/// {@template counter_cubit}
/// A [Cubit] which manages an [int] as its state.
/// {@endtemplate}
class CounterCubit extends Cubit<int> {
/// {@macro counter_cubit}
CounterCubit() : super(0);
/// Add 1 to the current state.
void increment() => emit(state + 1);
/// Subtract 1 from the current state.
void decrement() => emit(state - 1);
}
각 함수는 emit을 통해 상태를 업데이트하고, 이 변경이 BLoC or Cubit을 구독하는 위젯에 알려집니다.
- Counter View : lib/counter/view/counter_view.dart
현재 카운트를 렌더링하고 카운트를 증가/감소시키는 두 개의 FloatingActionButton을 렌더링하는 역할을 합니다.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_counter/counter/counter.dart';
/// {@template counter_view}
/// A [StatelessWidget] which reacts to the provided
/// [CounterCubit] state and notifies it in response to user input.
/// {@endtemplate}
class CounterView extends StatelessWidget {
/// {@macro counter_view}
const CounterView({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: BlocBuilder<CounterCubit, int>(
builder: (context, state) {
return Text('$state', style: textTheme.displayMedium);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
key: const Key('counterView_increment_floatingActionButton'),
child: const Icon(Icons.add),
onPressed: () => context.read<CounterCubit>().increment(),
),
const SizedBox(height: 8),
FloatingActionButton(
key: const Key('counterView_decrement_floatingActionButton'),
child: const Icon(Icons.remove),
onPressed: () => context.read<CounterCubit>().decrement(),
),
],
),
);
}
}
- Barrel : lib/counter/view/view.dart
이 부분은 Flutter 프로젝트에서 "barrel" 파일을 사용하여 모듈의 공개 인터페이스를 관리하는 방법을 설명합니다.
Barrel 파일은 특정 디렉토리의 모든 export를 한 곳에 모아, 다른 파일에서 이들을 쉽게 import할 수 있게 해줍니다.
export 'counter_page.dart';
export 'counter_view.dart';
따라서 view.dart 파일은 counter view의 모든 공개 부분을 export합니다
lib/counter/counter.dart
export 'cubit/counter_cubit.dart';
export 'view/view.dart';
counter.dart 파일은 counter feature의 모든 공개 부분을 export 하고,
cubit/counter_cubit.dart와 view/view.dart를 export하여,
이들을 사용하는 다른 파일에서는 단일 import를 통해 필요한 모든 부분에 접근할 수 있게 해줍니다.
Page하나를 만들떄의 과정을 정리 해보면
Page를 만들기전에 Cubit, View을 만들어야하고
Cubit을 만들기전에 필요한 경우 State가 만들어져있어야하고
State가 만들어져있으면 ->
Cubit에 Import해서 사용하고 ->
이걸가지고 Page에서
BlocProvider에 Cubit을 넣어주고
child에 View를 넣어주면 동작한다.
참고링크
https://bloclibrary.dev/#/fluttercountertutorial
https://bloclibrary.dev/#/coreconcepts?id=cubit-vs-bloc
'[Flutter]' 카테고리의 다른 글
[Flutter] Bloc Concurrency 라이브러리에 대해서 알아보자 (0) | 2023.10.20 |
---|---|
[Flutter] Equatable 라이브러리에 대해서 알아보자 (0) | 2023.10.20 |
[Flutter] BLoC - 1. BLoC (Business Logic Component)이란? (0) | 2023.10.19 |
[Flutter] FadeInImage를 이용하여 FadeIn 효과 + Placeholder Image 를 적용해보자 (0) | 2023.09.20 |
[Flutter] TabBar, Tab, TabBarView를 이용해서 탭 메뉴를 만들고, 탭 메뉴에 대한 화면을 만들어보자 (0) | 2023.09.20 |