StateProvider
StateProvider는 주로 React 애플리케이션에서 전역 상태 관리를 위해 사용되는 패턴입니다. 이 패턴은 Context API와 함께 useState나 useReducer 훅을 사용하여 상태를 관리하며, 애플리케이션의 여러 컴포넌트가 공통으로 사용하는 상태를 공유하고 업데이트할 수 있게 도와줍니다.역할
- 전역 상태 관리: 여러 컴포넌트에서 공통적으로 사용되는 데이터를 하나의 중앙화된 저장소(컨텍스트)에서 관리합니다. 이를 통해 트리의 깊은 하위 컴포넌트에서도 간편하게 상태를 접근하거나 수정할 수 있습니다.
- 상태 및 로직 공유:
StateProvider는 상태와 상태를 변경하는 로직을Provider컴포넌트를 통해 공유하고, 하위 컴포넌트에서useContext훅을 사용해 이를 구독합니다.
주요 구성 요소
- Context API: React의 내장 기능으로, 전역 데이터를 저장하고 이를 여러 컴포넌트에서 구독할 수 있게 해줍니다.
- Provider 컴포넌트:
StateProvider는Context.Provider로 상태를 전달하는 컴포넌트입니다. 모든 하위 컴포넌트는 이Provider로부터 상태를 받을 수 있습니다.
- Reducer (선택 사항):
useReducer훅을 통해 상태의 변경 로직을 정의할 수 있습니다. 상태가 복잡하거나 다양한 액션이 발생하는 경우useReducer가useState보다 더 적합합니다.

StateProvider 장단점
StateProvider의 장점:
- 간단한 상태 관리: 단일 상태 값을 관리하기 매우 간편합니다. 단순한 카운터, 토글, 기본 데이터 저장 등 단순한 상태 데이터를 보관할 때 적합합니다.
- 초기 학습 곡선이 낮음: Riverpod 상태 관리 도구 중 가장 간단하고 직관적이어서, 초기 사용자나 간단한 프로젝트에서 쉽게 사용할 수 있습니다.
- UI와의 연동 용이: 상태가 변경될 때 UI가 자동으로 업데이트되므로, 작은 규모의 애플리케이션에서는 쉽게 사용 가능합니다.
- 동기적 상태 변경:
StateProvider는 동기적인 상태 관리에 최적화되어 있어 복잡한 비동기 작업 없이도 상태를 관리할 수 있습니다.
- 테스트가 용이함: 테스트가 단순하고 상태 변화가 예측 가능하므로, 간단한 상태에 대한 테스트 코드 작성이 용이합니다.
StateProvider의 단점:
- 복잡한 상태 관리에 비효율적:
StateProvider는 단일 값을 다루는 데 적합하지만, 복잡한 비즈니스 로직이나 여러 상태 간의 상호작용이 필요한 상황에서는 적합하지 않습니다. 복잡한 상태를 다룰 때는StateNotifierProvider나FutureProvider,StreamProvider같은 더 강력한 도구가 필요합니다.
- 비동기 처리 제한:
StateProvider는 기본적으로 동기적 상태 관리에 초점을 맞추고 있기 때문에, 비동기 작업을 다룰 때는 추가적인 로직이 필요하며, 복잡해질 수 있습니다. 예를 들어, API 호출과 같은 비동기 작업이 필요한 경우FutureProvider또는StreamProvider를 사용하는 것이 더 적합합니다.
- 보일러플레이트 코드 증가: 간단한 상태 관리에는 적합하지만, 상태가 복잡해지면 그에 따라
StateProvider와 관련된 코드가 복잡해질 수 있습니다. 이 경우StateNotifierProvider나ChangeNotifierProvider같은 더 구조화된 방식이 더 효율적입니다.
- 리액티브 프로그래밍에 약함:
StateProvider는 리액티브 프로그래밍에 적합하지 않으며, 상태 변화가 발생할 때마다 값만 갱신하는 방식입니다. 비동기 데이터 스트림이나 반응형 데이터를 다룰 때는 더 복잡한 상태 관리 도구가 필요합니다.
StateNotifierProvider
StateNotifierProvider는 Riverpod 상태 관리 라이브러리에서 사용되는 주요 프로바이더 중 하나입니다. StateNotifier와 함께 사용하여 상태를 관리할 때 유용합니다. StateNotifier는 상태의 변화를 관리하고, StateNotifierProvider는 이 상태 변화를 구독하고 컴포넌트에 전달하는 역할을 합니다.StateNotifierProvider 기본 개념
- StateNotifier: 상태 변경 로직을 포함한 클래스입니다. 이 클래스를 사용하면 상태를
immutable하게 관리하면서 특정 상태를 변경할 수 있습니다.
- StateNotifierProvider:
StateNotifier가 관리하는 상태를 외부에서 접근하고 구독할 수 있도록 제공하는 역할을 합니다. 이를 통해StateNotifier가 관리하는 상태를 Riverpod 컨테이너를 통해 전역적으로 사용할 수 있게 됩니다.

장점
- Immutable한 상태 관리:
StateNotifier를 통해 상태를 불변으로 유지할 수 있습니다. 상태 변경은 항상 새로운 상태를 생성하는 방식으로 이루어지기 때문에, 상태 관리의 일관성이 높아집니다.
- 분리된 상태 변경 로직: 상태와 상태 변경 로직이
StateNotifier에 분리되어 있어 코드 구조가 깔끔하고 유지보수하기 쉽습니다.
- 간결한 상태 관리:
StateNotifierProvider는 상태 관리가 필요한 곳에 쉽게 상태를 전달하고 구독할 수 있게 해줍니다. 이로 인해 상태 관리 코드가 간결해집니다.
- 전역 상태 관리: Riverpod의 특성상 상태를 전역적으로 쉽게 관리할 수 있으며, 리액티브하게 변경 사항을 구독할 수 있습니다.
단점
- 학습 곡선: Flutter의 기본 상태 관리(
setState)보다는 복잡하고, Riverpod와StateNotifier개념을 처음 접하는 경우에는 학습에 시간이 필요합니다.
- 보일러플레이트 코드: 단순한 상태 관리라면
StateNotifier와StateNotifierProvider의 사용이 오히려 불필요한 코드를 늘릴 수 있습니다. 작은 앱이나 간단한 상태 관리에는 조금 과할 수 있습니다.
- 복잡한 상태 변경 관리: 상태가 복잡해질수록 상태 관리 로직도 복잡해질 수 있습니다. 특히 여러 액션과 다양한 상태를 관리해야 하는 경우에는 로직이 복잡해질 수 있습니다.
예제코드
1. main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverapp/num_notify_provider.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("HomePage");
return Scaffold(
body: Column(
children: [
Expanded(
child: Top(),
),
Expanded(
child: Bottom(),
),
],
),
);
}
}
class Bottom extends ConsumerWidget {
const Bottom({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
print("Bottom");
/*
NumStore store = ref.read(numProvider);
*/
BunStore store = ref.read(bunProvider.notifier);
return Center(
child: Container(
child: InkWell(
onTap: () {
print("증가 클릭됨");
store.increase();
},
child: Text(
"증가",
style: TextStyle(fontSize: 30),
),
),
),
);
}
}
//riverpod 사용 할 때만 ConsumerWidget 사용한다.
class Top extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
print("Top");
/* Provider 사용!
싱글톤으로 관리한다. 여러번 실행되도 한번만 뜬다. prvicer
NumStore store = ref.read(numProvider); //ref.read(numProvider) -> provider의 익명함수가 실행됨! (창고에 접근)
NumStore num2 = ref
.read(numProvider); //ref.read(numProvider) -> provider의 익명함수가 실행됨! (문법)
*/
//notifyProvider는 값에 바로 접근이 가능하다.
BunModel model = ref.watch(bunProvider); // provider의 익명함수가 실행됨! (모델에 접근)
return Center(
child: Container(
// "${Store.num}" = Provider , "${model.bun}" = notifyProvider
child: Text("${model.bun}", style: TextStyle(fontSize: 30)),
),
);
}
}코드설명
1.HomePage 클래스
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("HomePage");
return Scaffold(
body: Column(
children: [
Expanded(
child: Top(),
),
Expanded(
child: Bottom(),
),
],
),
);
}
}Scaffold: Flutter에서 기본 레이아웃을 설정하는 위젯입니다. 상단에 앱바를 넣거나 하단에 플로팅 액션 버튼을 추가할 수 있습니다.
Column: 두 개의 자식 위젯(Top,Bottom)을 세로로 배치합니다.
Expanded: 자식 위젯이 부모 위젯의 남은 공간을 모두 차지하도록 설정합니다.
2.Bottom 클래스 (ConsumerWidget)
class Bottom extends ConsumerWidget {
const Bottom({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
print("Bottom");
BunStore store = ref.read(bunProvider.notifier);
return Center(
child: Container(
child: InkWell(
onTap: () {
print("증가 클릭됨");
store.increase();
},
child: Text(
"증가",
style: TextStyle(fontSize: 30),
),
),
),
);
}
}ConsumerWidget: Riverpod에서 제공하는 상태 관리 위젯입니다.WidgetRef를 통해 프로바이더에 접근할 수 있습니다.
ref.read(bunProvider.notifier):bunProvider에서 상태를 관리하는BunStore객체에 접근합니다.increase()메소드를 통해 상태를 변경할 수 있습니다.
InkWell: 클릭 이벤트를 감지하여store.increase()메소드를 호출합니다. 이 메소드는BunStore에서 정의된 로직을 통해 숫자를 증가시키는 역할을 합니다.
3.Top 클래스 (ConsumerWidget)
class Top extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
print("Top");
BunModel model = ref.watch(bunProvider);
return Center(
child: Container(
child: Text("${model.bun}", style: TextStyle(fontSize: 30)),
),
);
}
}ref.watch(bunProvider): Riverpod의watch메소드를 통해bunProvider의 상태 변화를 구독합니다. 상태가 변경되면Top위젯이 리렌더링됩니다.
BunModel:bunProvider의 상태로,model.bun을 통해 숫자 값을 가져옵니다.
상태 관리
코드의 상태 관리 핵심은
num_notify_provider.dart에 정의된 bunProvider입니다. 이는 아마도 StateNotifier와 StateNotifierProvider를 사용하여 상태를 관리하는 방식일 것입니다.2. num_provider.dart
//sinppets
//관리자
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 항상 이형태를 유지하자! , 항상 일관성있게 코드를 만들자
//창고 데이터 (책임: 데이터)
class NumModel {
int num = 1;
}
//창고 (책임: 비즈니스 로직 관리) 부모꺼라서 super 써주기!
class NumStore extends NumModel {
void add() {
super.num++;
}
void minus() {
super.num--;
}
}
//창고 관리자는 return 값을 가진다. (책임: 창고 관리)
final numProvider = StateProvider<NumStore>((ref) {
print("StateProvider 창고 생성됨");
return NumStore();
});
코드설명
1. NumModel 클래스
class NumModel {
int num = 1;
}- NumModel: 상태를 나타내는 데이터 모델입니다. 이 클래스는 단순히
num이라는 정수 변수를 관리하며, 초기값으로 1을 가집니다.
- 이 모델은 상태의 기본 구조를 정의합니다.
2. NumStore 클래스
class NumStore extends NumModel {
void add() {
super.num++;
}
void minus() {
super.num--;
}
}- NumStore:
NumModel을 상속받아 상태 변경을 관리하는 클래스입니다.
- 여기서 비즈니스 로직이 포함되며,
add()메소드는num값을 증가시키고,minus()메소드는num값을 감소시킵니다.
super.num++은 부모 클래스인NumModel의num값을 직접 조작하는 방식입니다.
3. StateProvider 설정
final numProvider = StateProvider<NumStore>((ref) {
print("StateProvider 창고 생성됨");
return NumStore();
});- StateProvider: Riverpod에서 제공하는 프로바이더 중 하나로, 상태를 관리하는 객체를 제공합니다.
- 여기서
numProvider는 NumStore 인스턴스를 제공하며, 이를 통해 앱에서NumStore의 상태를 전역적으로 접근하고 변경할 수 있습니다.
StateProvider를 사용하면 간단한 상태 관리를 할 수 있습니다. 여기서는NumStore객체가 관리되며, 이를 통해add()와minus()를 호출하여num값을 변경할 수 있습니다.
동작 원리
- 초기 상태: 앱이 시작될 때
StateProvider가 실행되면서NumStore객체가 생성됩니다. 이 객체는 초기값으로num = 1을 가집니다.
- 상태 변경:
NumStore객체의add()나minus()메소드를 호출하면num값이 변경됩니다.
- UI 반영: Riverpod을 통해
numProvider를 구독하고 있는 UI가 있으면, 상태 변경이 발생할 때마다 UI가 리렌더링되어 변경된 값을 반영합니다.
일관된 코드 스타일 유지
- 일관성 있는 코드 작성: 여기서 강조한 부분은 일관성 있게 코드를 작성하는 것입니다. 이 코드는 항상 상태를 모델(
NumModel), 비즈니스 로직(NumStore), 상태 관리자(StateProvider)로 나누어 관리하고 있습니다. 이를 통해 코드의 유지보수성과 가독성을 높일 수 있습니다.
3. num_notify_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
//창고데이터 (책임: 데이터)
class BunModel {
int bun;
BunModel(this.bun);
}
//창고 (책임: 비즈니스 로직관리)
class BunStore extends StateNotifier<BunModel> {
BunStore(super.state);
void increase() {
//기존 값을 알고 나서 값을 다시 넘겨준다. 깊은 복사를 해줘야한다. new를 새로해줘야한다.
BunModel model = state;
model.bun++;
//변경된 값 깊은 복사
super.state = BunModel(model.bun);
}
void decrease() {
BunModel model = state;
super.state = BunModel(model.bun--);
}
}
//창고관리자 (책임: 창고 관리)
final bunProvider = StateNotifierProvider<BunStore, BunModel>((ref) {
BunModel model = BunModel(1);
return BunStore(model);
});
코드설명
1. BunModel 클래스
class BunModel {
int bun;
BunModel(this.bun);
}- BunModel: 상태 데이터를 정의하는 클래스입니다. 이 클래스는
bun이라는 정수 값을 가지고 있으며, 이를 통해 현재 상태를 나타냅니다.
- 생성자:
BunModel(this.bun)을 통해bun값을 초기화할 수 있습니다.
2. BunStore 클래스 (StateNotifier 상속)
class BunStore extends StateNotifier<BunModel> {
BunStore(super.state);
void increase() {
BunModel model = state;
model.bun++;
super.state = BunModel(model.bun); // 깊은 복사를 통해 상태를 변경
}
void decrease() {
BunModel model = state;
super.state = BunModel(model.bun--); // 감소 처리
}
}- BunStore:
StateNotifier<BunModel>를 상속받아 상태 변경 로직을 관리합니다.StateNotifier는 상태 변경 시 UI를 자동으로 갱신할 수 있도록 해줍니다.
- increase 메소드:
increase()는 현재 상태(state)를 가져와bun값을 증가시킨 후, 새로 생성한BunModel로 상태를 갱신합니다. - 깊은 복사: 상태는 불변성(immutable)을 유지해야 하므로, 상태를 직접 수정하지 않고 새로운 인스턴스를 생성하여 상태를 업데이트합니다.
- decrease 메소드:
decrease()도 유사한 방식으로bun값을 감소시키며, 새로운BunModel을 만들어 상태를 갱신합니다.
3. bunProvider (StateNotifierProvider)
final bunProvider = StateNotifierProvider<BunStore, BunModel>((ref) {
BunModel model = BunModel(1);
return BunStore(model);
});- StateNotifierProvider:
bunProvider는 Riverpod에서 상태 관리 및 상태 변경을 관리하는 프로바이더입니다. BunStore: 상태를 변경할 로직을 담고 있습니다.BunModel:bunProvider가 관리하는 초기 상태로,bun값은 1로 초기화됩니다.
- 사용법: 이 프로바이더는 UI에서
ref.watch나ref.read를 통해 상태를 구독하고 변경할 수 있습니다.
상태 관리 흐름
- 초기 상태: 앱이 시작될 때
bunProvider가BunModel의 초기값1을 가진BunStore객체를 생성하여 상태를 관리합니다.
- 상태 변경: 사용자가 UI에서
increase()또는decrease()를 호출하면,BunStore에서 상태를 새로 생성하여 Riverpod의 상태로 반영됩니다.
- UI 업데이트: 상태가 변경되면
StateNotifier를 통해 UI에 자동으로 상태 변경이 반영됩니다.
4. 결과 화면

Share article