[Flutter] Cubit

uengmin·2025년 7월 1일

Flutter

목록 보기
20/20

이전 글에서 다뤘던 Bloc 라이브러리에 내장된 또다른 상태 관리 방식인 cubit은 bloc의 복잡한 부분을 간편하게 사용할 수 있게 해주고 있다.

Cubit은 bloc에 비하면 정말 심플하고 가볍다. 프로젝트를 진행하다 가벼운 기능과 상태는 cubit으로 사용할 수 있도록 해줬기에 bloc이 많은 선택을 받을 수 있지 않았을까 라는 생각이 된다.

dependencies

bloc: ^8.1.0
flutter_bloc: ^8.1.1

Count App

카운터 앱은 Flutter 프로젝트 최초 생성시 기본으로 있는 카운트 앱을 약간 변형하여 리셋 기능을 추가하고 단순히 카운트 상태를 증가/감소만 하는 것이 아닌 얼마 만큼을 증가/감소 시킬지에 대한 상태를 추가하여 해당 값 만큼 증가/감소하는 기능을 가지게끔 만든 예제이다.
모든 상태관리 예제는 해당 기능을 가진 카운트 앱으로 만들어봐야지.

UI

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

Cubit은 bloc에 비해 심플하고 event 개념이 없기에 provider, get과 유사한 상태 흐름을 보여주고 있다.

count_state.dart

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];
}

Count_cubit.dart

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));
  }
}

결과

0개의 댓글