Equatable 2탄

equatable | Dart Package

Equatable 사용해 보기 1편

지난 글에 이어서 Equatable 라이브러리에 대해서 알아보도록 하자.

지난 글에서 class equal 기능이 필요한 이유와 사용 법에 대해서 다뤄봤는데, 아직도 왜 사용해야 하는지에 대해서 의문이 생겼을 것이다. class가 같은지 다른지에 대해서 구분이 필요한 이유가 과연 무엇일까 ?
최대한 간단한 예제로 빠르게 확인해보자.

Equatable 라이브러리를 가장 자주 사용하는 경우가 바로 Bloc Pattern을 상태 관리로 사용하는 경우일 것이다. 저도 Bloc 사용 외에는 사실 Equatable을 사용하지는 않고 있다.

Bloc에서 왜 Equatable을 사용해야 하는지에 대해서 알아보고, 다른 상태 관리 라이브러리들과 어떤 차이가 있는지에 대해서 확인해보자.

Flutter

간단한 카운트 앱을 만들 것인데, 카운트를 증가시키는 버튼과 단순히 현재의 카운트 상태를 리턴하는 버튼을 배치하여 상태 변경이 빌드에 호출되는 차이점에 대해서 확인해 보려고 한다.
비교를 위해서 Equatable을 사용하는 Bloc Pattern과 GetX, Provider로 각각 생성하였다.

pubspec.yaml

dependencies:
	equatable: ^2.0.5
    flutter_bloc: ^8.1.1
    get: ^4.6.5
    provider: ^6.0.4

Get X

우선 GetxController를 만들어 보자.
count 변수를 선언해주고 카운트를 1씩 올려주는 increment 함수를 만들었다. 현재의 count 상태를 리턴해주는 stateReturn 함수도 생성해 보자.

class LibraryEquatableGetx extends GetxController {
  int count = 0;

  void increment() {
    count = count + 1;
    update();
  }

  void stateReturn() {
    count;
    update();
  }

  
  void onInit() {
    count = 0;
    super.onInit();
  }
}

Provider

위에서 생성한 기능과 동일하게 Provider로 생성하도록 하자.

class LibraryEquatableProvider extends ChangeNotifier {
  int _count = 0;

  void increment() {
    _count = _count + 1;
    notifyListeners();
  }

  void stateReturn() {
    _count;
    notifyListeners();
  }

  int get count => _count;
}

Bloc

이어서 동일한 기능을 가지는 Bloc도 생성해보자.
Equatable을 상속받아 count 상태를 생성하고 카운트 증가, 카운트 상태 리턴에 필요한 이벤트를 생성하자.

state, event를 상속받는 bloc을 만들고, count 상태를 1올려주는 increment 함수와 현재 상태를 리턴만 하는 return 함수를 만들면 된다.

state

class LibraryEquatableState extends Equatable {
  final int count;
  const LibraryEquatableState(this.count);

  
  List<Object?> get props => [count];
}

event

abstract class LibraryEquatableEvent extends Equatable {}

class IncrementEquatableEvent extends LibraryEquatableEvent {
  
  List<Object?> get props => [];
}

class ReturnEquatableEvent extends LibraryEquatableEvent {
  
  List<Object?> get props => [];
}

bloc

class LibraryEquatableBloc
    extends Bloc<LibraryEquatableEvent, LibraryEquatableState> {
  LibraryEquatableBloc() : super(const LibraryEquatableState(0)) {
    on<IncrementEquatableEvent>(_increment);
    on<ReturnEquatableEvent>(_return);
  }

  void _increment(
      IncrementEquatableEvent event, Emitter<LibraryEquatableState> emit) {
    emit(LibraryEquatableState(state.count + 1));
  }

  void _return(
      ReturnEquatableEvent event, Emitter<LibraryEquatableState> emit) {
    emit(LibraryEquatableState(state.count));
  }
}

UI

각 상태를 비교하기 위해 수직으로 위젯을 배치하였다.

각각 빌더 구조로 State Management를 생성하여 터치시 빌드 콜이 어떻게 다른지 비교하면 된다.

GetX, Provider, Bloc 모두 증가 버튼을 클릭하면 상태가 1씩 올라가면서 빌드 콜이 찍히는 것을 볼 수있다. 이제 Return 버튼을 터치해 보자.

여기서 차이점이 나타나는데, 현재 상태인 count 값을 리턴을 하는데, 실제 count 변수의 값에는 차이가 없다. 하지만 GetX, Provider에서는 빌드 콜이 호출되는 것을 확인할 수 있다.

Bloc은 어떤 결과가 나올까 ? 변경되지 않은 상태에 대해서는 빌드 콜이 호출되지 않고 있다.
바로 Equatable이 Object를 비교해 변경이 되지 않은 상태에서는 불필요한 호출을 하지 않고 있는 것이다.

Scaffold(
      appBar: appBar(title: "Equatable Library"),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ...getX
          ...provider
          ...bloc
        ],
      ),
    );

Get X

GetBuilder<LibraryEquatableGetx>(
              init: LibraryEquatableGetx(),
              builder: (state) {
                logger.d("Get X ${state.count}");
                return _stateUI(
                    context: context,
                    title: "Get X",
                    count: state.count,
                    onIncrement: () => state.increment(),
                    onReturn: () => state.stateReturn());
              }),

Provider

ChangeNotifierProvider(
            create: (_) => LibraryEquatableProvider(),
            child: Consumer<LibraryEquatableProvider>(
                builder: (context, state, child) {
              logger.d("Provider ${state.count}");
              return _stateUI(
                  context: context,
                  title: "Provider",
                  count: state.count,
                  onIncrement: () => state.increment(),
                  onReturn: () => state.stateReturn());
            }),
          ),

Bloc

BlocProvider(
            create: (_) => LibraryEquatableBloc(),
            child: BlocBuilder<LibraryEquatableBloc, LibraryEquatableState>(
                builder: (context, state) {
              logger.d("Bloc ${state.count}");
              return _stateUI(
                  context: context,
                  title: "Bloc",
                  count: state.count,
                  onIncrement: () => context
                      .read<LibraryEquatableBloc>()
                      .add(IncrementEquatableEvent()),
                  onReturn: () => context
                      .read<LibraryEquatableBloc>()
                      .add(ReturnEquatableEvent()));
            }),
          ),

아래 코드는 stateUI 코드이다.

Padding _stateUI({
    required BuildContext context,
    required String title,
    required int count,
    required Function() onIncrement,
    required Function() onReturn,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 48, left: 20, right: 20),
      child: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              title,
              style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 32),
            ),
            Row(
              children: [
                _button(title: "$count", onTap: onIncrement),
                const SizedBox(width: 12),
                _button(title: "Return", onTap: onReturn),
              ],
            ),
          ],
        ),
      ),
    );
  }
 GestureDetector _button({
    required String title,
    required Function() onTap,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: 70,
        height: 70,
        decoration: BoxDecoration(
          color: const Color.fromRGBO(115, 115, 115, 1),
          borderRadius: BorderRadius.circular(60),
        ),
        child: Center(
          child: Text(
            title,
            style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
          ),
        ),
      ),
    );
  }

Git

https://github.com/boglbbogl/flutter_velog_sample/tree/main/lib/library/equatable

마무리

위에서 확인한 방법은 단순한 구조이며, Get, Provider에서도 빌드에 대한 상태 변경 호출을 제어할 수 있는 방법은 있다. 여기서는 단순히 Equtable로 Bloc을 생성하는 차이에 대해서 살펴보고, Equatable 라이브러리에 대해서 알아보기 위해서 작성된 방식일 뿐이기에 오해 하시면 안됩니다.

이렇게 포스팅 두 개를 통해서 Equtable에 대해서 알아보았다. 필수 라이브러리는 아니지만 사용해보면 확실히 좋은 라이브러리인 것 같다.

profile
Flutter Developer

1개의 댓글

comment-user-thumbnail
2024년 2월 26일

GetX, Provider 에 비해 Bloc 코드는 코드가 좀 복잡도가 있어서 한눈에 잘 안들어 오는 단점이 있군요

답글 달기