이전 글에서 다뤘던 Bloc 라이브러리에 내장된 또다른 상태 관리 방식인 cubit은 bloc의 복잡한 부분을 간편하게 사용할 수 있게 해주고 있다.
Cubit은 bloc에 비하면 정말 심플하고 가볍다. 프로젝트를 진행하다 가벼운 기능과 상태는 cubit으로 사용할 수 있도록 해줬기에 bloc이 많은 선택을 받을 수 있지 않았을까 라는 생각이 된다.
bloc: ^8.1.0
flutter_bloc: ^8.1.1
카운터 앱은 Flutter 프로젝트 최초 생성시 기본으로 있는 카운트 앱을 약간 변형하여 리셋 기능을 추가하고 단순히 카운트 상태를 증가/감소만 하는 것이 아닌 얼마 만큼을 증가/감소 시킬지에 대한 상태를 추가하여 해당 값 만큼 증가/감소하는 기능을 가지게끔 만든 예제이다.
모든 상태관리 예제는 해당 기능을 가진 카운트 앱으로 만들어봐야지.
Stack countScreenPublicUI({
required BuildContext context,
required int count,
required int selectCount,
required Function() onIncrement,
required Function() onDecrement,
required Function() onReset,
required Function(int) onCount,
}) {
return Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: Center(
child: Text(
"$count",
style: const TextStyle(
fontSize: 60, fontWeight: FontWeight.bold),
),
)),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: onIncrement,
child: const Icon(
Icons.add_circle_outline,
size: 40,
),
),
const SizedBox(width: 24),
GestureDetector(
onTap: onDecrement,
child: const Icon(
Icons.remove_circle_outline,
size: 40,
),
)
],
),
const SizedBox(height: 24),
GestureDetector(
onTap: onReset,
child: Container(
width: MediaQuery.of(context).size.width / 3,
height: 48,
decoration: BoxDecoration(
color: const Color.fromRGBO(71, 71, 71, 1),
borderRadius: BorderRadius.circular(12)),
child: const Center(
child: Text(
'Reset',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
),
),
const SizedBox(height: 40),
],
),
Positioned(
top: 20,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Padding(
padding: const EdgeInsets.only(left: 20),
child: Column(
children: [
countAppSelectedCountBox(
onTap: onCount, selectNumber: selectCount, number: 1),
countAppSelectedCountBox(
onTap: onCount, selectNumber: selectCount, number: 10),
countAppSelectedCountBox(
onTap: onCount, selectNumber: selectCount, number: 20),
countAppSelectedCountBox(
onTap: onCount, selectNumber: selectCount, number: 50),
countAppSelectedCountBox(
onTap: onCount, selectNumber: selectCount, number: 100),
],
),
),
),
),
],
);
}
GestureDetector countAppSelectedCountBox({
required Function(int) onTap,
required int number,
required int selectNumber,
}) {
return GestureDetector(
onTap: () => onTap(number),
child: Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: selectNumber == number
? const Color.fromRGBO(91, 91, 91, 1)
: const Color.fromRGBO(61, 61, 61, 1),
borderRadius: BorderRadius.circular(12)),
child: Center(
child: Text(
'$number',
style: TextStyle(
fontWeight: FontWeight.bold,
color: selectNumber == number
? Colors.white
: const Color.fromRGBO(155, 155, 155, 1)),
)),
),
),
);
}
Cubit의 생성 방식은 bloc과 똑같이 생성하고 사용할 수 있다.
차이점은 cubit에는 event가 없기에 context.read로 접근하여 바로 기능을 호출할 수 있다는 점
return BlocProvider<CountAppCubitCubit>(
create: (context) => CountAppCubitCubit(),
child: BlocBuilder<CountAppCubitCubit, CountAppCubitState>(
builder: (context, state) {
return Scaffold(
appBar: appBar(title: 'Count App With Cubit'),
body: countScreenPublicUI(
context: context,
count: state.count,
selectCount: state.selectCount,
onIncrement: () {
HapticFeedback.mediumImpact();
context.read<CountAppCubitCubit>().increment();
},
onDecrement: () {
HapticFeedback.mediumImpact();
context.read<CountAppCubitCubit>().decrement();
},
onReset: () {
HapticFeedback.mediumImpact();
context.read<CountAppCubitCubit>().reset();
},
onCount: (int number) {
HapticFeedback.mediumImpact();
context.read<CountAppCubitCubit>().select(number);
},
),
);
},
),
);
Cubit은 bloc에 비해 심플하고 event 개념이 없기에 provider, get과 유사한 상태 흐름을 보여주고 있다.
class CountAppCubitState extends Equatable {
final int count;
final int selectCount;
const CountAppCubitState({
this.count = 0,
this.selectCount = 1,
});
CountAppCubitState copyWith({
int? count,
int? selectCount,
}) {
return CountAppCubitState(
count: count ?? this.count,
selectCount: selectCount ?? this.selectCount,
);
}
List<Object> get props => [count, selectCount];
}
class CountAppCubitCubit extends Cubit<CountAppCubitState> {
CountAppCubitCubit() : super(const CountAppCubitState());
void increment() {
emit(state.copyWith(count: state.count + state.selectCount));
}
void decrement() {
emit(state.copyWith(count: state.count - state.selectCount));
}
void reset() {
emit(state.copyWith(count: 0));
}
void select(int count) {
emit(state.copyWith(selectCount: count));
}
}
