ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Flutter | 플러터 상태관리 패키지 사용해보기 - bloc - 4. bloc 과 cubit 사용방법
    개발/Flutter 2023. 2. 24. 22:02
    반응형

    이번에는 bloc와 cubit의 구조에 대해서 알아보겠습니다. 그리고 간단한 예제를 만들어 보겠습니다.그리 많은 분들이 사용하는 GetX와 간단 비교해보겠습니다.


    1. GetX 동작

    GetX는 presentation 영역과 비지니스 로직 영역으로 나뉘어서 관리됩니다. 일반변수와 상태변수가 있고 이벤트 함수가 있습니다. 이벤트함수는 환면에서 직접 호출을 해서 비지니스 로직을 통해 변수나 클래스를 처리하는 방식입니다. GetX는 상태를 여러가지를 Controller에 넣어서 관리를 합니다. 한가지 Controller에서 2가지 변수와 이벤트를 각각 따로 이용하도록 2가지를 같이 사용할 수도 있고, 2가지를 나눠서 연관이 있는 부분끼리 묶어서 2가지의 Controller로 나눠서 사용할 수도 있습니다.


    2. Cubit 구조

    Cubit은 Cubit과 State로 나눠집니다. 그리고 GetX의 Controller과 비슷합니다. 하지만 GetX는 여러개의 변수를 사용할 수 있지만 Cubit는 한개만 존재합니다. 물론 Cubit<>안에 Class를 넣어서 여러개를 관리 할 수 있지만 기본적으로 한가지만 관리한다고 보시면 됩니다.그리고 반환하는 값의 초기값을 지정해 줘야 합니다. 화면에서는 함수를 직접적으로 바로 호출해서 사용할 수 있습니다. 반환값을 state라고 사용하고 그 값을 이용해서 이벤트에서 사용해주고 emit을 이용해서 적용해줍니다. emit은 cubit 내에서 사용할 수 있습니다. Cubit도 GetX처럼 2가지를 같이 한 Controller에 사용할 수도 있고 나눠서 사용할 수 있습니다. 나눠서 사용하는것이 유지보수나 코드 재사용이 더 유용할 것으로 보입니다.


    3. Bloc 구조

    cubit에 비해서 복잡하다고 볼 수 있습니다. Bloc와 Event, State로 나눠집니다. GetX나 Cubit은 화면에서 직접 호출하여 비지니스 로직을 수행합니다. 하지만 Bloc는 직접 호출이 아닌 Event를 만들어 주고 Stream으로 event를 담아주면 그 이벤트에 해당하는 부분이 호출되면서 비지니스 로직이 수행되는 방식입니다. 직관적이지 않고 복잡하지만 이 부분이 있더라도 좋은 이유가 있는데 이 부분은 나중에 다뤄보도록 하겠습니다. Bloc의 상속을 받고 2가지의 Generic을 받는데 event와 state를 넣어줍니다. 넣은 event만 받을 수 있기때문에 abstract로 만들어 주고 그 하위에 여러 이벤트를 만들어 주고 상위의 event를 상속받게 해서 사용해줍니다. 그리고 on<>을 등록하고 호출해서 사용해줍니다. on 안의 generic을 지정해주고 해당 이벤트만 사용할 수 있습니다.. 그리고 함수는 (event, emit) => (); (event, emit){} 으로 사용됩니다.


    실습 - 1. GetX

    새로운 프로젝트를 만들어 주겠습니다. 그리고 lib에 src 폴더를 추가해 줍니다. 그 안에 app.dart 파일을 만들어 줍니다. StatelessWidget로 App을 만들어 주고 main.dart에서 불러올 수 있게 해줍니다. 하단의 temianl 창에서 flutter pub add get bloc flutter_bloc equatable 를 입력해서 4가지 패키지를 설치해 줍니다. get은 bloc와 비교를 위해서 설치해 줍니다. 개발하는 남자님의 유튜브에서 코드를 다운받으셔도 좋습니다. 우선 app.dart에 기본 화면을 만들어 주겠습니다. appbar의 title에는 Text로 더하기 예제로 작성해 주시고 body에는 Padding로 all로 40을 해주시면 됩니다. 그 안에 Column으로 mainAxisAlignment를 center로, crossAxisAlignmnet를 stretch로 주시면 됩니다. 그리고 children에 ElevatedButton을 3개 만들어 주신 후 Text로 GetX 더하기, Cubit 더하기, Bloc 더하기 로 만들어 주신 후 onPressed는 현재는 (){}으로 아무 동작도 하지 않게 만들어 주시면 됩니다. useMaterial3를 사용하게 되면서 각 화면에서 appbar를 지정해주더라도 변화가 없습니다. 그래서 main.dart에서 appBarTheme를 수정해 주시면 됩니다.

    appBarTheme: const AppBarTheme(
      backgroundColor: Colors.blue,
      foregroundColor: Colors.white,
      actionsIconTheme: IconThemeData(color: Colors.white),
      iconTheme: IconThemeData(color: Colors.white),
    ),

    material2와 비슷하게 사용하실 수 있습니다. 그리고 우선 제일 먼저 GetX를 살펴보기 위해서 src에 getx 폴더를 만들어 줍니다. 그리고 getx_home.dart를 만들어주고 StatelessWidget으로 GetXHome 클래스를 만들어 주시면 됩니다. Scaffold로 만들어 주시고 appbar에는 text로 GetX 상태관리, body에는 Center안에 Text로 0, style에 fontSize를 80으로 우선 만들어 줍니다. 그리고 main.dart의 MaterialApp을 GetMaterialApp으로 변경해줍니다. 그리고 app.dart에서 첫번째 버튼의 (){} 안에 GetXHome으로 이동하게 작성해 줍니다.

    ElevatedButton(
      onPressed: () {
        Get.to(GetXHome());
      },
      child: const Text('GetX 더하기'),
    ),

    그리고 getx_home.dart에서 floatingActionButton을 추가해 주고 onPressed는 빈 값으로 해줍니다. 이 버튼은 눌렀을때 더하기 기능이 작동하도록 해주는 버튼입니다. 이를 위해서 getx폴더에 count_getx_controller.dart를 만들어 주고 GetxController을 상속하 클래스를 만들어 줍니다. 그리고 RxInt로 count 는 0을 만들어 주고 void로  addCount 함수와 subtractCount 함수를 만들어 줍니다.

    class CountGetXController extends GetxController {
      RxInt count = 0.obs;
    
      void addCount(){
        count(count.value + 1);
      }
    
      void subtractCount(){
        count(count.value -1);
      }
    }

    그리고 Get.to로 페이지를 이동할때 binding을 해줄 수 있습니다. context를 등록해 줘야 하기 때문입니다. BindingsBuilder((){})로 Get.Put(CountGetXController())을 해주시면 CountGetXController 등록 해주시면 됩니다. duration옵션에 Duration.zero를 넣어주시면 됩니다.

    onPressed: () {
      Get.to(
        const GetXHome(),
        binding: BindingsBuilder(() {
          Get.put(CountGetXController());
        }),
        duration: Duration.zero,
      );
    },

    그리고 getx_home.dart에서 StatelessWidget에서 상속을 GetView<CountGetxController>로 변경 해주시면 됩니다. 이를 통해서 위젯 안에서 controller에 접근하게 해줍니다.  그리고 Center의 child 옵션에 Obx로 자동 완성을 해주 신 뒤에 null 부분에 Text를 넣어주시면 됩니다. 그리고 0으로 입력했던 부분을 count를 보여주기 위해서 controller.count.toString()로 변경해주시면 됩니다. 그리고 덧셈, 뺄셈 버튼을 만들기 위해 FloatingActionButton을 Row로 감싸주고 버튼을 2개 만들어 줍니다. mainAxisAlignment는 center로 해주시면 됩니다. 두개의 버튼의 child에 icon으로 더하기에는 Icons.add, 빼기에는 Icons.remove를 입력해 주시고 빼기 버튼에는 backgroundColor을 red로 해주시면 됩니다. 두 버튼이 너무 붙어있으니 사이 SizedBox의 width를 30을 줘서 사이를 띄워놓겠습니다.

    그리고 기능을 넣어주기 위해서 두 버튼의 onPressed에 더하기에는 controller.addCount(), 빼기에는 controller.subtractCount()를 넣어주시면 됩니다.

    정상 작동하는것을 볼 수 있습니다. 위와 같이 GetX에서 상태관리를 쉽게 할 수 있습니다.


     

    실습 - 2. Cubit

    이번에는 cubit을 활용해보겠습니다. src에 cubit 폴더를 만들고 cubit_home.dart를 만들어 줍니다. 그리고 StatelessWidget으로 CubitHome 클래스를 만들어 주겠습니다. getx의 home과 화면이 똑같으므로 복사해서 그대로 붙여주겠습니다. 그리고 app.dart에서 두번째 버튼을 똑같이 Get.to를 이용해서 이동시켜주겠습니다. 이때 GetX를 사용하지 않으므로 binding은 사용하지 않고 duration만 똑같이 zero로 해줍니다. cubit_home.dart로 이동해서 appbar의 title을 변경해줍니다. 그리고 cubit 폴더에 count_cubit.dart를 만들어 줍니다. Cubit<int>를 상속받는 CountCubit를 만들어 줍니다. 그럼 클래스명에 빨간줄이 뜨는데 Alt + Enter를 눌러서 만들어 줍니다.

    CountCubit(super.initialState);

    해당 코드처럼 나오는 것으로 사용 할 수도 있는데 이 방법은 Cubit를 만들때 값을 초기화를 해줄수 있는 방법입니다.

    CountCubit() : super(0);

    뒤에서 위 방법과 다른점을 비교해보겠습니다. 우선 app.dart로 이동해 주고 CubitHome으로 페이지로 이동하는 부분에서 child에서 CubitHome()으로 바로 보내는게 아닌 BlocProvider로 감싸준 뒤 create를 작성해 줍니다. 이때 위 코드와 같이 사용했을때는 create에 (context) => CountCubit(0)으로 해주면서 초기값을 지정해는 방법으로 사용하시면 되고, 아래 코드처럼 이미 초기값을 지정해주는 경우에는 CountCubit()로 사용해서 넘겨주는 값이 없이 해당 클래스에서 초기화한 값을 사용하는 방법이 있습니다. 이제 CountCubit에 void로 addCount와 subtractCount 함수를 만들어 주겠습니다. cubit는 state가 아닌 emit함수로 값을 넣어줍니다.

    class CountCubit extends Cubit<int> {
      // CountCubit(super.initialState); //만들때 값을 초기화 시켜줄 수 있음
      CountCubit() : super(0);
    
      void addCount() {
        emit(state + 1);
      }
    
      void subtractCount() {
        emit(state - 1);
      }
    }

    이제 값이 변하는 부분을 수정해 주겠습니다. cubit_home.dart에서 Text부분을 변화시켜 줍니다. Center의 child에 BlocBuilder을 사용해줍니다. generic에는 CoountCubit와 int를 넣어줍니다. builder 옵션에는 (context,state)가 되고 return에 Text로 값이 반환되는 부분인 state를 이용해서 state.toString()로 값을 보여주시면 됩니다.

    body:  Center(
      child: BlocBuilder<CountCubit,int>(
        builder: (context,state) {
          return Text(
            state.toString(),
            style: const TextStyle(
              fontSize: 80,
            ),
          );
        }
      ),
    ),

    이제 버튼에 함수를 추가해 주면 됩니다. context에서 CountCubit를 read해와서 더해주는 함수인 addCount()를 사용해 줍니다. 빼기에는 subtractCount()함수를 사용해 주시면 됩니다.

    FloatingActionButton(
      onPressed: () {
        context.read<CountCubit>().addCount();
      },
      child: const Icon(Icons.add),
    ),
    const SizedBox(
      width: 30,
    ),
    FloatingActionButton(
      onPressed: () {
        context.read<CountCubit>().subtractCount();
      },
      backgroundColor: Colors.red,
      child: const Icon(Icons.remove),
    ),

    재 실행 해보면 정상 작동하는 것을 볼 수 있습니다. 간단하게 잘 사용할 수 있는것을 볼 수 있습니다.


    실습 - 3. Bloc

    마지막으로 Bloc를 살펴보겠습니다. src 폴더에 bloc 폴더를 만들어 준 뒤, bloc_home.dart를 만들어 줍니다. StatelessWidget의 BlocHome 클래스를 만들어 주고 CubitHome과 비슷하므로 해당 build를 그대로 복사 붙여넣기를 해줍니다. 또 bloc 폴더에 count_bloc.dart를 만들어 주고 우선 CountBloc 클래스를 만들어 준 후, 하단에 Bloc상속을 받을 때 필요한 event를 만들어 주겠습니다. 우선 abstract없이 Event를 만들어 보겠습니다. AddCountEvent를 만들어 주시고 Equatable을 상속받게 해줍니다. 그러면 빨간줄이 뜨는데 Alt + Enter로 override 해주시면 됩니다.

    class AddCountEvent extends Equatable {
      @override
      List<Object?> get props => throw UnimplementedError();
    }
    

    그리고 상단의 CountBloc에서 Bloc를 상속하게 해주고 그 인자로 AddCountEvent를 넣어주고 반환값으로 int를 넣어주시면 됩니다. 그러면 빨간줄이 뜨는데 똑같이 Alt + Enter로 super를 넣어주시거나 0로 초기화 해주시면 됩니다.

    class CountBloc extends Bloc<AddCountEvent, int> {
      CountBloc() : super(0);
    }

    그리고 addCount 함수를 추가해 주면 되는데 Cubit처럼 그냥 함수로 추가가 아닌 on으로 구독을 받고 사용을 하게 해줘야 합니다.

    CountBloc() : super(0) {
      on<AddCountEvent>((event, emit) => {emit(state + 1)});
    }

    그리고 subtract를 추가해 줘야 하는데 event를 하나만 상속받기 때문에 추가해 줄 수 없습니다. 그래서 하단의 event를 변경해 주겠습니다. 우선 abstract를 추가해주고 count를 관리하므로 add를 클래스명에서 빼주시면 됩니다. 그리고 override 한 부분도 삭제해 줍니다.

    abstract class CountEvent extends Equatable {}

    그리고 이 CountEvent를 상송하는 AddCountEvent와 SubtractCountEvent를 만들어 줍니다.

    class AddCountEvent extends CountEvent {}
    class SubtractCountEvent extends CountEvent {}

    이 두개의 클래스에 override를 추가해 주시면 됩니다.

    class AddCountEvent extends CountEvent {
      @override
      List<Object?> get props => throw UnimplementedError();
    }
    class SubtractCountEvent extends CountEvent {
      @override
      List<Object?> get props => [];
    }

    개발하는남자님의 코드를 보면 아래의 override같이 하는데 제 버전이 최신이라서 그런지 위와 같이 override가 되는데 현재까지 차이는 없어 보입니다. 그리고 CountBloc에서 Bloc의 generic의 event를 CountEvent로 변경해줍니다. 그리고 AddCountEvent인 on을 복사해서 Subtract로 변경하고 state-1로 변경해주시면 됩니다.

    class CountBloc extends Bloc<CountEvent, int> {
      CountBloc() : super(0) {
        on<AddCountEvent>((event, emit) => {emit(state + 1)});
        on<SubtractCountEvent>((event, emit) => {emit(state - 1)});
      }
    }

    그리고 BlocHome으로 와서 BlocBuilder을 import해주고  CountBloc를 import해주시면 됩니다. appbar의 title도 Bloc 상태관리로 변경해주시면 됩니다. 그리고 페이지를 넘겨주는 부분인 app.dart로 이동해줍니다. 역시 Get.to에 BlocProvider를 넣어주시고 create에는 CountBloc를 넣어줍니다. child에는 BlocHome으로 해주시고 duration 역시 똑같이 zero로 해줍니다. 다시 BlocHome으로 와서 버튼에 이벤트를 추가해 주겠습니다. Cubit와 똑같이 context에서 CountBloc를 read해주는것 까지 같지만 add 합수에 AddCountEvent()를 넣어주시면 추가가 됩니다.

    context.read<CountBloc>().add(AddCountEvent());

    SubtractCountEvent()도 똑같이 넣어주신 뒤 실행해 보겠습니다.


    오늘은 getx와 cubit, bloc를 비교하며 예제를 봤습니다. 자세한 사항은 공식 문서나 개발하는남자님의 유튜브를 더 보시면 좋을것 같습니다.

    반응형

    댓글

Designed by Tistory.