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