[Flutter] 상태 관리 9편(State Management) - Mobx

Tyger·2023년 1월 15일
3

State Management

목록 보기
9/14

상태 관리 9편(State Management) - Mobx

상태 관리(State Management) 1편 - State Ful
상태 관리(State Management) 2편 - Value Listenerable
상태 관리(State Management) 3편 - Get X [Simple]
상태 관리(State Management) 4편 - Get X [Reactive]
상태 관리(State Management) 5편 - Provider
상태 관리(State Management) 6편 - Bloc
상태 관리(State Management) 7편 - Cubit
상태 관리(State Management) 8편 - Riverpod

Top 7 Flutter State Management Libraries In 2022
Most Popular Packages for State Management in Flutter (2023)

mobx | Flutter Packages

이번 글은 상태관리 라이브러리들 간의 간단한 사용 방법에 대해서 작성한 마지막 시리즈로 Mobx에 대해서 작성해 보도록 하겠다.

Mobx는 지금까지 알아본 다른 라이브러리들과 큰 차이점이 하나 있는데, 바로 Code generator를 활용하는 라이브러리라는 점이다.

Code generator는 build_runner를 사용해서 생성하는 코드 자동 생성기라고 생각하면 된다.
주로 freezed와 같은 json, deep copy 등의 반복되는 코드를 자동으로 생성시킬 때 사용하는 dart 기능 중 하나다.

Mobx에서는 Observables, Actions, Reactions 이렇게 3개의 중요한 개념이 있는데, 이 부분은 dart packages에 설명이 자세히 나와있다.

dependencies

mobx: ^2.1.3

dev_dependencies

Code generator를 사용하기 위해서 아래 dependencies도 함께 추가해 주어야한다.

mobx_codegen: ^2.1.1
build_runner: ^2.3.3

Count App

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

모든 상태관리 예제는 해당 기능을 가진 카운트 앱으로 만들어 볼 것이다.

UI

앞으로 모든 상태관리에 동일한 UI파일을 사용할 거여서 상태관리 편에서 UI 내용은 다른 글과 동일할 것이다.

UI는 가운데 카운트를 보여줄 숫자가 있고 바로 하단 Row위젯안에 더하기, 마이너스 아이콘을 배치해뒀다. 그 아래로 reset 기능을 호출할 버튼을 만들었다.

카운트 기능을 사용하는게 단순히 숫자만 올리고 내리는 것이 아니라 얼만큼을 증가시키고 감소시킬지를 선택할 수 있는 넘버 박스들을 왼쪽 상단에 수직으로 배치하여 구성하였다.

여기서는 간단한 상태 관리만 보여주는 정도의 UI여서 다른 글에서 각각의 상태 관리에 대해서 더 깊숙하고 복잡한 UI 구조를 만들어서 사용해 볼 예정이다.

아래 공유한 Git Repository를 방문하면 소스 코드를 오픈해 뒀습니다 !

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

CountAppMobx를 생성해준다.

final CountAppMobx _counter = CountAppMobx();

Observer()위젯의 빌더를 사용하여 상태를 소비할 곳에 넣어준다.

Scaffold(
        appBar: appBar(title: 'Count App With MobX'),
        body: Observer(
          builder: (_) => countScreenPublicUI(
            context: context,
            count: _counter.count,
            selectCount: _counter.selectCount,
            onIncrement: () {
              HapticFeedback.mediumImpact();
              _counter.increment();
            },
            onDecrement: () {
              HapticFeedback.mediumImpact();
              _counter.decrement();
            },
            onReset: () {
              HapticFeedback.mediumImpact();
              _counter.reset();
            },
            onCount: (int number) {
              HapticFeedback.mediumImpact();
              _counter.select(number);
            },
          ),
        ));

Mobx

Mobx 기능은 code generator를 사용할 것이기에 구문 규칙을 아래와 같이 생성해 준뒤, 터미널에 명령어를 넣어 코드를 생성할 수 있도록 해준다.

flutter pub run build_runner watch --delete-conflicting-outputs

이렇게 코드를 생성하면 g.dart라는 파일이 하나 생기는데, 우리가 방금 만든 파일에서 필요로하는 기능과 코드가 g.dart 파일에 자동으로 생성이 되는 것이다.

import 'package:mobx/mobx.dart';

part 'count_app_mobx.g.dart';

class CountAppMobx = _CountAppMobx with _$CountAppMobx;

abstract class _CountAppMobx with Store {
  
  int count = 0;

  
  int selectCount = 1;

  
  void increment() {
    count = count + selectCount;
  }

  
  void decrement() {
    count = count - selectCount;
  }

  
  void reset() {
    count = 0;
  }

  
  void select(int number) {
    selectCount = number;
  }
}

Result

Git

https://github.com/boglbbogl/flutter_velog_sample/tree/main/lib/count_app/mobx

마무리

이렇게 Stateful 부터 상태관리 라이브러리를 활용한 상태 관리 방법까지 총 9가지의 다양한 방법으로 같은 기능을 만들어 보았다.

하나씩 사용해 보면서 차이점을 느끼고 적합한 기능에 맞는 상태 관리 방법을 도입하면 된다.

상태 관리는 개발에 반드시 필요한 영역이고, 상태를 제어할 수 있어야 원하는 앱을 만들 수 있다.

지금까지 알아본 9가지 상태 관리 방법은 Git 저장소에 공유한 상태이고, 실행시켜서 값을 바꿔보면서 연습해보면 이해하는데 더 수월할 것이다.

profile
Flutter Developer

0개의 댓글