RiverPod 학습 이유
오늘은 개인 프로젝트에서 RiverPod을 사용하여 데이터 상태관리하는 방법을 학습하려고 한다.
왜 많은 데이터 상태 관리하는 방법 중 RiverPod인가? 거창한 이유는 없다.
나는 프레임워크(SwiftUI , Flutter) 개발을 하였지만 데이터 상태관리에 대한 식견이 부족하다.
그래서 솔직히 다른 개발자분들이 말씀하시는 “어떤 상태관리 패키지(혹은 라이브러리)는 무엇이 좋다.” 혹은 “무엇이 별로다” 이야기는 내가 경험해봐야 이해할 수 있다고 생각한다.
그리고 회사마다 자격요건에 상태관리 툴도 모두 다르다. 하지만 데이터 상태관리 툴 하나를 깊게 공부하였다면 다른 상태관리를 쉽게 학습할 수 있을 것이다.
데이터 상태관리 툴
RiverPod을 바로 학습하기전에 Data State Tool들을 간단하게 특징을 정리해봤다.
Provider
- Flutter 팀에서 권장하는 상태 관리 패턴이다.
- 간단하고 직관적인 API를 제공하며, 의존성 주입 및 상태 관리를 쉽게 할 수 있다.
- ChangeNotifier를 사용하여 상태 변화를 감지하고 UI를 업데이트 한다.
Riverpod
- Provider의 발전된 형태로, 더 나은 성능과 안전성을 제공한다.
- 전역 상태 관리가 가능하며, 컴포넌트 간의 의존성을 명확하게 관리할 수 있다.
- 테스트가 용이하고, 비동기 작업을 쉽게 처리할 수 있는 기능을 제공한다.
Bloc (Business Logic Component):
- 이벤트 기반 아키텍처를 사용하여 상태 관리를 수행한다.
- RxDart 라이브러리를 활용하여 스트림을 통해 상태를 관리한다.
- 복잡한 비즈니스 로직을 분리하여 테스트 가능성을 높이고, 재사용성을 향상시킨다.
GetX
- 경량화된 상태 관리 솔루션으로, 간단한 API와 높은 성능을 자랑한다.
- 반응형 프로그래밍을 지원하며, 라우팅 및 의존성 주입 기능도 포함되어 있다.
- 코드의 양을 줄이고, 개발 속도를 높일 수 있다.
상태관리
그러면 데이터 상태관리가 정확히 무엇인지 알아보자.
아래 gif 파일은 Flutter: 공식 문서 State management 에서 보여주는 반응협 앱의 상태관리 방법이다
선언형 UI(Declarative UI)
플러터는 선언형 UI라고 한다. 이 말은 Flutter는 앱의 현재 상태를 반영하도록 User Interface를 구축한다.
예를 들어, 앱의 상태가 변경되면(사용자가 설정 화면에서 Toggle을 한 경우), 사용자가 상태를 변경하면 UI가 다시 그려진다. 개발자가 UI 자체를 필수적으로 변경할 필요가 없는 것이다.
명령형 UI와 선언형 UI차이점
나는 명령형 UI 프로그래밍 개발을 경험해본적이 없다. 첫 개발을 SwiftUI 로 접한 나는 UI를 수동으로 구성한 후, UI가 변경될 때의 상황을 변경하는 코드를 필수로 작성해본적이 없다.
만약 수동으로 모든 상태에 따라 UI를 변경해줘야 하는 코드를 작성해야 한다면 귀찮다고 생각할 것 이다.
“이걸 왜 내가 해야지?”, “원래 자동으로 되야 하는거 아니야?” 라는 의견이 나올 것이다.(선언형 UI만 경험했기에)
위의 이미지를 보면서 차이점을 이해해보자.
명령형 UI로 화면 그리기
Java에서는 b 배경색은 노랑색, c1, c2가 있는 상황 → b 배경색 빨강색으로 변경, c1, c2 모두 초기화, c3 추가됨 명령어를 사용하는 것이 명령형 스타일인 것이다. 절차적이기도 하다.
// Imperative style
b.setColor(red) // b 배경색 빨강색으로 변경
b.clearChildren() // c1, c2 모두 초기화
ViewC c3 = new ViewC(...) // c3 선언
b.add(c3) // c3 추가
선언형 UI로 화면 그리기
Dart에서는 아래 코드를 사용하면 된다. StatefulWidgets에서 setState() 호출을 통해 UI를 변경할 수 있다.
훨씬 간단하다.
// Declarative style
return ViewB(
color: red,
child: const ViewC(),
);
Ephemeral state 와 App State
공식문서를 보면 앱 상태(App State), 일시적인 상태(Ephemeral state)에 대한 소개가 나온다.
가장 넓은 의미에서 앱의 상태는 앱이 실행될 때 메모리에 존재하는 모든 것을 말한다고 한다.
모든 것은, assets, Flutte가 갖고 있는 UI, 애니메이션 상태, 텍스처, 글꼴등에 값을 갖고 있는 변수를 의미한다.
이미지나 비디오 같은 그래픽 자원은 Flutter가 처리해주기 때문에 개발자가 상태 관리를 할 필요가 없다.
그래서 상태(State)에 대한 정의는 ‘UI를 재구성하기 위해 필요한 모든 데이터’이다.
그렇다면 개발자가 관리해야하는 상태(State)는 무엇이 있을까? 일시적인 상태(Ephemeral state)와 앱 상태(App State)가 있다.
Ephemeral state
일시적 상태(때때로 UI 상태 또는 로컬 상태라고도 함)는 하나의 위젯에 깔끔하게 포함할 수 있는 상태이다.
이게 무슨의미일까? 예시를 보자.
- current page in a PageView
- 복잡한 애니메이션의 현재 진행 상황
- 현재 선택된 탭 BottomNavigationBar
이러한 상태에 접근할 필요가 거의 없다. 복잡한 방식으로 변경되지도 않다.
이러한 상태에 대해 상태 관리 기법등을 사용할 필요가 없다. 이때 필요한것이 StatefulWidget이다.
class MyHomepage extends StatefulWidget {
const MyHomepage({super.key});
@override
State<MyHomepage> createState() => _MyHomepageState();
}
class _MyHomepageState extends State<MyHomepage> {
int _index = 0;
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: _index,
onTap: (newIndex) {
setState(() {
_index = newIndex;
});
},
// ... items ...
);
}
}
위에 setState() StatefulWidget의 State 클래스 내 필드는 완전히 자연스러운 것이다.
_index 변수는 내부에서만 변경된다. MyHomepage 위젯은 사용자가 앱을 닫고 재시작을 해도 _index는 0으로 재설정된다.
App State
일시적지 않은 상태를 앱 상태라고 하는데 앱의 여러 부분에서 공유하고자 하는 상태, 사용자 간의 유지하고자 하는 상태(공유 상태라고도 한다.)
- App State의 예시를 보자.
- 사용자 설정(User preference)
- 로그인 정보(Login info)
- 어플의 알림
- 전자상거래 앱의 장바구니
- 뉴스 앱에서 기사 읽기/쓰기 상태 읽기
State 그리고. setState() 앱에서 모든 상태를 관리한다. 그렇다고 절대적인 규칙이라고 볼수는 없다. 정확한 규칙은 없기에 개발자가 일시적인 상태로 시작하지만 기능이 증가하며 앱 상태로 전환이 가능하다.
아래 다이어그램을 보면 언제 App state를 사용하는 지, Ephemeral state를 사용하는 지 예시를 볼 수 있다.
Flutter에서 State management하는 방법
물론 Flutter에 있는 방식으로 상태 관리를 할 수 있다.
UI를 재구성하는 방식으로 공식 문서를 보면 MyCart Widget 예제를 통해 알 수 있다.
위의 다이어그램은 오른쪽에 있는 화면 동작에 대한 예시이다.
Catalog에는 AppBar에 장바구니와 Body에는 List가 있다.
사용자가 ADD 버튼을 눌러서 추가를 한다면 장바구니에 추가가 되어야 한다.
그래서 MyCatalog를 통해 MyAppBar와 MyListitem이 연결되어 있다.
이때 ADD 버튼을 누르게 되면 선언형 UI Flutter는 UI를 변경하기위해 rebuild를 해야 한다.
어떻게 방법을 변경하는지 좋은 예시와 안좋은 예시를 보자.
안좋은 예시
위젯을 인스턴스화하여 위젯에 있는 메소드를 외부에서 호출하여 외부에서 변경하도록 하는 것은 안좋은 예시이다.
// BAD: DO NOT DO THIS
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
만약 위의 코드처럼 작동을 하고싶게 한다면 아래 처럼 해야 한다. 그럼에도 아래 코드도 좋지 않다.
// BAD: DO NOT DO THIS
Widget build(BuildContext context) {
return SomeWidget(
// The initial state of the cart.
);
}
void updateWith(Item item) {
// Somehow you need to change the UI from here.
}
좋은 예시
그렇다면 어떤 방법이 좋은 예시일까?
Flutter에서는 콘텐츠가 변경될 때마다 새로운 위젯을 구성한다.
// GOOD
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
// GOOD
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
// Just construct the UI once, using the current state of the cart.
// ···
);
}
위의 코드는 아래의 특징을 같고 있다.
- 불변성: 위젯의 상태가 변경될때 메서드를 호출하는것이 아닌 생성자를 사용해서 위젯을 생성하는 것이다.
- 부모 위젯에서 상태 관리: 상위 위젯에서 관리하여 자신의 상태를 관리하지 않고 필요한 데이터를 받을 수 있다. 데이터의 흐름이 명확하고 위젯간 의존성이 줄어든다.
데이터 전달
상위 위젯에서 하위 위젯에게 데이터를 전달 하려면 하위 위젯의 프로퍼티로 호출할때 값을 넣어주면 된다.
그렇다면 하위 위젯에서 상위 위젯의 데이터를 변경하는 방법은 콜백 함수로 변경할 수 있다.
@override
Widget build(BuildContext context) {
return SomeWidget(
// Construct the widget, passing it a reference to the method above.
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
위의 코드로서 SomeWidget(부모위젯)에서 MyListItem(하위위젯)을 갖고 있다. 이때 콜백 함수로 상위 위젯에 있는 데이터를 하위위젯에서 변경할 수 있는 것이다.
결론
위의 과정은 Simple State Management를 통해 Widget 간 data를 주고받는 과정을 컨트롤 한것이다.
Simple State Management는 구현이 단순해서 많이 쓰이는데 복잡한 앱에서도 일부 작은 영역 간의 State변환은 Simple State Management를 이용하기도 합니다.
복잡한 과정 많은 콜백을 전달해야 하는 상황은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만든다.
위젯간의 종속성이 심해지기 때문이다. 앱의 기능을 변경하거나 삭제하려할 때 너무 귀찮은 작업이 된다.
이러한 상황 때문에 데이터 상태 관리 도구(Data State management Tool)를 사용한다.
'Flutter' 카테고리의 다른 글
[Flutter] Riverpod 학습 - Performing side effects & Passing arguments to your requests (0) | 2025.01.07 |
---|---|
[Flutter] RiverPod - provider 알아보기(with 네트워크 요청) (0) | 2025.01.06 |
[Flutter] [Error Handling] Result Pattern (간편로그인 적용) (0) | 2024.12.26 |
[Flutter] [Error Handling] Result Pattern (1) | 2024.12.24 |
[Flutter] [Firbase] 간편 로그인 구현하기(Apple🍎) (0) | 2024.12.24 |