상태 관리(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) 9편 - Mobx
Top 7 Flutter State Management Libraries In 2022
Most Popular Packages for State Management in Flutter (2023)
이번 시간에는 저도 처음 접해본 Riverpod에 대해서 글을 작성하도록 하겠다.
Riverpod는 provider를 개발한 사람이 개발한 라이브러리로 provider의 확장판 같은 느낌이라고 다른 글에서 설명하고 있다.
provider에는 큰 단점이 존재하는데 바로 런타임시 에러와 타입의 제한 사항등이 있다.
이러한 단점을 보완해서 다시 만든게 riverpod이다. 하지만 riverpod는 아직 프로덕션 단계에서의 도입에 대해서 주의를 기울이라고 하고 있으며, 개인적으로 riverpod를 30분 정도 배워봤는데, 아직까지는 꼭 provider를 대체하여 사용해야할 필요성 까지는 느끼지 못하겠고, provider와 결합해서 사용하는 정도로 사용하면 좋을 것 같다는 느낌을 받았다.
riverpod: ^2.1.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)),
)),
),
),
);
}
Riverpod의 변수를 생성할 때 StateProvider형태의 값을 생성하여 준다.
final StateProvider<int> _count = StateProvider<int>((ref) => 0);
final StateProvider<int> _selectCount = StateProvider((ref) => 1);
위젯을 생성할 때 StatelessWidget 부분을 ConsumerWidget으로 변경해 준 뒤 build에 WidgetRef를 추가해 준다.
class CountScreenWithRiverpod extends ConsumerWidget {
...
Widget build(BuildContext context, WidgetRef ref) {
...
UI에 값을 노출할 때는 ref.watch(count)값으로 보여주면 되고 ref.read의 update 기능을 활용하여 원하는 값으로 변경해 주면 된다.
Scaffold(
appBar: appBar(title: 'Count App With Riverpod'),
body: countScreenPublicUI(
context: context,
count: ref.watch(_count),
selectCount: ref.watch(_selectCount),
onIncrement: () {
HapticFeedback.mediumImpact();
ref
.read(_count.notifier)
.update((state) => state + ref.watch(_selectCount));
},
onDecrement: () {
HapticFeedback.mediumImpact();
ref
.read(_count.notifier)
.update((state) => state - ref.watch(_selectCount));
},
onReset: () {
HapticFeedback.mediumImpact();
ref.read(_count.notifier).update((state) => 0);
},
onCount: (int number) {
HapticFeedback.mediumImpact();
ref.read(_selectCount.notifier).update((state) => number);
},
),
);
Riverpod 라이브러리는 저도 처음 접해본 라이브러리여서 간단하게 공부해서 작성을 해보았지만 원래 저렇게 사용하지는 않는 것 같은데 신기해서 저렇게 사용을 해보았다.
조금 더 공부가 필요하다.
다음 글에서는 riverpod와 같이 처음 공부해본 라이브러리이지만 현재 여러 프로젝트에서 지금 당장 도입해도 손색이 없을 것 같은 상태 관리인 Mobx에 대해서 작성하도록 하겠다.