상태 관리(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에 대해서 작성해 보도록 하겠다.
Mobx는 지금까지 알아본 다른 라이브러리들과 큰 차이점이 하나 있는데, 바로 Code generator를 활용하는 라이브러리라는 점이다.
Code generator는 build_runner를 사용해서 생성하는 코드 자동 생성기라고 생각하면 된다.
주로 freezed와 같은 json, deep copy 등의 반복되는 코드를 자동으로 생성시킬 때 사용하는 dart 기능 중 하나다.
Mobx에서는 Observables, Actions, Reactions 이렇게 3개의 중요한 개념이 있는데, 이 부분은 dart packages에 설명이 자세히 나와있다.
mobx: ^2.1.3
Code generator를 사용하기 위해서 아래 dependencies도 함께 추가해 주어야한다.
mobx_codegen: ^2.1.1
build_runner: ^2.3.3
카운터 앱은 Flutter 프로젝트 최초 생성시 기본으로 있는 카운트 앱을 약간 변형하여 리셋 기능을 추가하고 단순히 카운트 상태를 증가/감소만 하는 것이 아닌 얼마 만큼을 증가/감소 시킬지에 대한 상태를 추가하여 해당 값 만큼 증가/감소하는 기능을 가지게끔 만든 예제이다.
모든 상태관리 예제는 해당 기능을 가진 카운트 앱으로 만들어 볼 것이다.
앞으로 모든 상태관리에 동일한 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 기능은 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;
}
}
https://github.com/boglbbogl/flutter_velog_sample/tree/main/lib/count_app/mobx
이렇게 Stateful 부터 상태관리 라이브러리를 활용한 상태 관리 방법까지 총 9가지의 다양한 방법으로 같은 기능을 만들어 보았다.
하나씩 사용해 보면서 차이점을 느끼고 적합한 기능에 맞는 상태 관리 방법을 도입하면 된다.
상태 관리는 개발에 반드시 필요한 영역이고, 상태를 제어할 수 있어야 원하는 앱을 만들 수 있다.
지금까지 알아본 9가지 상태 관리 방법은 Git 저장소에 공유한 상태이고, 실행시켜서 값을 바꿔보면서 연습해보면 이해하는데 더 수월할 것이다.