StateProvider란?
StateProvider는 Riverpod에서 가장 기본적인 상태 관리 도구입니다. 간단한 값 하나를 저장하고, 그 값을 읽거나 변경할 수 있게 해주는 Provider예요.
쉽게 말해서 "전역 변수의 안전한 버전"이라고 생각하면 됩니다. 일반 변수와 달리 StateProvider는 값이 변경될 때마다 그 값을 사용하는 모든 위젯을 자동으로 다시 그려줍니다.
쉽게 말해서 "전역 변수의 안전한 버전"이라고 생각하면 됩니다. 일반 변수와 달리 StateProvider는 값이 변경될 때마다 그 값을 사용하는 모든 위젯을 자동으로 다시 그려줍니다.
언제 StateProvider를 사용하는가?
StateProvider는 간단한 상태에만 사용해야 합니다.
사용하기 좋은 경우:
• 숫자 카운터 (좋아요 수, 장바구니 개수)
• true/false 토글 (다크모드, 알림 설정)
• 문자열 입력값 (검색어, 사용자명)
• 선택된 인덱스 (현재 탭, 선택된 아이템)
사용하면 안 되는 경우:
• 여러 필드가 있는 복잡한 객체
• 리스트나 맵 같은 복잡한 데이터 구조
• 비즈니스 로직이 들어가는 복잡한 상태
만약 사용자 정보처럼 이름, 이메일, 나이가 모두 들어있는 객체를 관리하려면 StateProvider 대신 StateNotifierProvider를 사용해야 합니다.
사용하기 좋은 경우:
• 숫자 카운터 (좋아요 수, 장바구니 개수)
• true/false 토글 (다크모드, 알림 설정)
• 문자열 입력값 (검색어, 사용자명)
• 선택된 인덱스 (현재 탭, 선택된 아이템)
사용하면 안 되는 경우:
• 여러 필드가 있는 복잡한 객체
• 리스트나 맵 같은 복잡한 데이터 구조
• 비즈니스 로직이 들어가는 복잡한 상태
만약 사용자 정보처럼 이름, 이메일, 나이가 모두 들어있는 객체를 관리하려면 StateProvider 대신 StateNotifierProvider를 사용해야 합니다.
기본 사용법
1. StateProvider 만들기
Dart
// 카운터 (숫자)
final counterProvider = StateProvider<int>((ref) => 0);
// 다크모드 (true/false)
final darkModeProvider = StateProvider<bool>((ref) => false);
// 검색어 (문자열)
final searchProvider = StateProvider<String>((ref) => '');
StateProvider를 만들 때는 초기값을 지정해야 합니다. 위 예제에서는 카운터는 0, 다크모드는 false, 검색어는 빈 문자열로 시작합니다.
2. 상태 읽기
Dart
class CounterDisplay extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('현재 카운트: $count');
}
}
ref.watch()
를 사용해서 상태를 읽습니다. 이렇게 하면 카운터 값이 바뀔 때마다 이 위젯이 자동으로 다시 그려집니다.3. 상태 변경하기
Dart
class CounterButtons extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Row(
children: [
ElevatedButton(
onPressed: () {
// 직접 값 변경
ref.read(counterProvider.notifier).state++;
},
child: Text('+1'),
),
ElevatedButton(
onPressed: () {
// 함수로 값 변경
ref.read(counterProvider.notifier).update((state) => state - 1);
},
child: Text('-1'),
),
],
);
}
}
상태를 변경할 때는
ref.read().notifier.state
를 사용합니다. 직접 값을 대입하거나 update()
함수를 사용할 수 있어요.실무 예제
다크모드 토글
Dart
final darkModeProvider = StateProvider<bool>((ref) => false);
class DarkModeSwitch extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isDark = ref.watch(darkModeProvider);
return Switch(
value: isDark,
onChanged: (value) {
ref.read(darkModeProvider.notifier).state = value;
},
);
}
}
스위치를 켜고 끄면 다크모드 상태가 바뀝니다. 이 상태를 앱 전체에서 사용해서 테마를 바꿀 수 있어요.
현재 탭 관리
Dart
final selectedTabProvider = StateProvider<int>((ref) => 0);
class TabView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedIndex = ref.watch(selectedTabProvider);
return Scaffold(
body: [HomeTab(), SearchTab(), ProfileTab()][selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedIndex,
onTap: (index) {
ref.read(selectedTabProvider.notifier).state = index;
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '홈'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: '검색'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '프로필'),
],
),
);
}
}
탭을 터치하면 선택된 탭 인덱스가 바뀌고, 그에 따라 보여지는 화면도 바뀝니다.
주의사항
복잡한 객체는 사용하지 마세요
Dart
// ❌ 이렇게 하지 마세요
class User {
final String name;
final String email;
final int age;
User({required this.name, required this.email, required this.age});
}
final userProvider = StateProvider<User>((ref) => User(name: '', email: '', age: 0));
// 이름만 바꾸려면?
ref.read(userProvider.notifier).state = User(
name: '새이름',
email: ref.read(userProvider).email, // 기존값 그대로 복사해야 함
age: ref.read(userProvider).age, // 기존값 그대로 복사해야 함
);
이렇게 하면 코드가 복잡해지고 실수하기 쉽습니다. 복잡한 객체는 StateNotifierProvider를 사용하세요.
상태 초기화
Dart
class ResetButton extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// 초기값으로 돌아가기
ref.read(counterProvider.notifier).state = 0;
// 또는 Provider 자체를 리셋
ref.invalidate(counterProvider);
},
child: Text('초기화'),
);
}
}
다른 Provider와 비교
vs Provider
• Provider: 한 번 만들어지면 절대 안 바뀌는 값 (설정, 서비스 객체)
• StateProvider: 앱 실행 중에 바뀔 수 있는 값 (카운터, 토글)
vs StateNotifierProvider
• StateProvider: 간단한 값 (int, bool, String)
• StateNotifierProvider: 복잡한 객체나 여러 필드가 있는 상태
vs FutureProvider
• StateProvider: 즉시 사용할 수 있는 값
• FutureProvider: 비동기 작업(API 호출 등)의 결과
• Provider: 한 번 만들어지면 절대 안 바뀌는 값 (설정, 서비스 객체)
• StateProvider: 앱 실행 중에 바뀔 수 있는 값 (카운터, 토글)
vs StateNotifierProvider
• StateProvider: 간단한 값 (int, bool, String)
• StateNotifierProvider: 복잡한 객체나 여러 필드가 있는 상태
vs FutureProvider
• StateProvider: 즉시 사용할 수 있는 값
• FutureProvider: 비동기 작업(API 호출 등)의 결과
정리
StateProvider는 Riverpod의 가장 기본적인 상태 관리 도구입니다. 간단한 값 하나를 전역적으로 관리하고 싶을 때 사용하세요.
핵심 포인트:
• 간단한 타입(int, bool, String)만 사용
•
• 복잡한 객체는 StateNotifierProvider 사용
• 상태가 바뀌면 자동으로 UI 업데이트
StateProvider만 잘 활용해도 카운터, 토글, 탭 선택 등 앱에서 자주 사용하는 간단한 상태들을 효과적으로 관리할 수 있습니다.
다음 편 예고: 다음 글에서는 불변 값을 관리하는 Provider에 대해 알아보겠습니다.
핵심 포인트:
• 간단한 타입(int, bool, String)만 사용
•
ref.watch()
로 읽기, ref.read().notifier.state
로 쓰기• 복잡한 객체는 StateNotifierProvider 사용
• 상태가 바뀌면 자동으로 UI 업데이트
StateProvider만 잘 활용해도 카운터, 토글, 탭 선택 등 앱에서 자주 사용하는 간단한 상태들을 효과적으로 관리할 수 있습니다.
다음 편 예고: 다음 글에서는 불변 값을 관리하는 Provider에 대해 알아보겠습니다.