일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- HTML
- GitHub
- typescript
- 애니메이션
- 닷넷
- MS-SQL
- Binding
- Flutter
- .NET
- MSSQL
- Animation
- listview
- Maui
- MVVM
- 깃허브
- 바인딩
- db
- 리엑트
- spring boot
- React JS
- 오류
- 플러터
- 파이어베이스
- 함수
- 마우이
- AnimationController
- 자바스크립트
- Firebase
- JavaScript
- page
- Today
- Total
개발노트
48. [Flutter] Riverpod Provider 종류와 사용법(with MVVM 패턴) 본문
Riverpod
Riverpod은 Flutter 애플리케이션에서 상태 관리를 위한 훌륭한 도구 중 하나입니다. 다양한 Provider 유형을 사용하여 효과적으로 상태를 관리할 수 있습니다.
- Riverpod 링크: https://riverpod.dev/ko/
Provider 종류
1. Provider:
**Provider**는 가장 기본적인 상태를 제공하는 역할을 합니다. 여기서는 사용자 이름을 제공하는 Provider를 예시로 들겠습니다.
final usernameProvider = Provider<String>((ref) => 'John Doe');
2. StateProvider:
**StateProvider**는 변경 가능한 상태를 제공합니다. 예를 들어, 카운터의 초기 값이 0인 경우:
final counterProvider = StateProvider<int>((ref) => 0);
3. FutureProvider:
**FutureProvider**는 비동기 작업의 결과를 제공합니다. 예를 들어, 네트워크에서 데이터를 가져오는 경우:
final fetchDataProvider = FutureProvider<List<String>>((ref) async {
// 비동기 작업 수행 (예: 네트워크 요청)
List<String> data = await fetchDataFromServer();
return data;
});
4. AsyncNotifierProvider, NotifierProvider
**NotifierProvider**는 Notifier를 수신하고 노출하는 데 사용되며,
**AsyncNotifierProvider**는 비동기 Notifier를 수신하고 노출하는 데 사용됩니다.
AsyncNotifier는 비동기적으로 초기화할 수 있는 Notifier입니다.
class AuthNotifier extends AsyncNotifier<bool> {
AuthNotifier() : super(const AsyncLoading());
Future<void> signIn() async {
state = AsyncLoading();
bool success = await performSignIn();
state = success ? AsyncData(true) : AsyncError('Sign-in failed');
}
}
final authNotifierProvider = AsyncNotifierProvider<AuthNotifier, AsyncValue<bool>>((ref) {
return AuthNotifier();
});
5. StreamProvider:
**StreamProvider**는 주기적으로 값을 생성하는 가상의 스트림을 만듭니다. 예를 들어, 실시간 채팅 메시지를 처리하는 경우:
final chatMessageStreamProvider = StreamProvider<List<String>>((ref) {
return getChatMessageStream();
});
이렇게 하면 각 Provider가 특정 상황에 맞는 의미 있는 데이터를 제공하고 관리할 수 있습니다.
final chatMessageStreamProvider = StreamProvider.autoDispose<List<String>>((ref) {
return getChatMessageStream();
});
StreamProvider.autoDispose 처럼 .autoDispose를 붙여 자동으로 자원을 해제하는 방식을 선택하여 실시간으로 값을 가져오는 Stream 특성을 고려하여 조회 비용을 줄일 수 있습니다. (다른 화면이용 중 Stream이 자동으로 dispose 됨)
Riverpod Provider 중 NotifierProvider를 예시로 작성해보겠습니다. (+ AsyncNotifierProvider와 비교)
Provider를 MVVM 패턴과 함께 사용할 때 좋기 때문에 하나의 시트에 MVVM 패턴을 적용하여 예시를 만들었습니다.
View, ViewModel, Model 각각의 역할 정리
NotifierProvider 사용한 예제
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
//Model: State로 관리할 User 객체를 만들어줌
class User {
final String name;
final bool autoLogin;
User({required this.name, required this.autoLogin});
}
//ViewModel: 사용할 Model을 초기화하고 함께 사용할 메서드를 정의함, + Riverpod Provider 종류를 결정함
//AsyncNotifierProvider를 사용한다면 AsyncNotifier, NotifierProvider를 사용한다면 Notifier 를 extends 로 상속하면됨
class UserViewModel extends Notifier<User> {
User user = User(name: "name", autoLogin: false);
@override
User build() {
return User(name: "name", autoLogin: false);
//build를 통한 초기화 부분(null방지)
}
User getUser() {
return User(name: state.name, autoLogin: state.autoLogin);
//state를 통해 user정보 가져오기
}
void setUserName(value) {
user = User(name: value, autoLogin: state.autoLogin);
state = user;
//name Set 하고 state 최신화
}
void setUserAuthLogin(value) {
user = User(name: state.name, autoLogin: value);
state = user;
//name Set 하고 state 최신화
}
}
final userProvider = NotifierProvider<UserViewModel, User>(
() => UserViewModel(),
);
//View: 위에서 생성한 ViewModel의 Provider을 실제 이용하는 화면
//ref로 provider를 사용하기 위해 ConsumerStatefulWidget 혹은 ConsumerWidget를 이용함
//이해를 돕기위해 두가지 모두 사용한 예시,
//ConsumerStatefulWidget으로 전체 틀을 만들고, ConsumerWidget으로 autoLogin 체크박스를 만들어봄
class UserPage extends ConsumerStatefulWidget {
const UserPage({super.key});
//위젯트리에서 ref를 사용하기 위해 StatefulWidget 대신 ConsumerStatefulWidget 사용
@override
UserPageState createState() => UserPageState();
//UserPageState로 createState 변경
}
class UserPageState extends ConsumerState<UserPage> {
//ConsumerState는 ConsumerStatefulWidget과 한 세트임
final TextEditingController _textEditingController = TextEditingController();
@override
void initState() {
_textEditingController.addListener(() {});
super.initState();
}
@override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 200,
width: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
ref.watch(userProvider).name,
style: const TextStyle(fontSize: 40),
),
const StateSwitch(),
Expanded(
child: TextField(
maxLength: 10,
style: const TextStyle(fontSize: 25),
controller: _textEditingController,
)),
TextButton(
onPressed: () => ref
.read(userProvider.notifier)
.setUserName(_textEditingController.value.text),
child: const Text(
'이름바꾸기',
style: TextStyle(fontSize: 30),
)),
],
),
),
),
);
}
}
class StateSwitch extends ConsumerWidget {
const StateSwitch({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return SwitchListTile.adaptive(
title: const Text("AutoLogin"),
value: ref.watch(userProvider).autoLogin,
//watch로 리빌드함
onChanged: (value) =>
ref.read(userProvider.notifier).setUserAuthLogin(value));
//메서드 사용은 provider에 .notifier를 붙여줘야함(read는 리빌드하지 않음)
}
}
주요 소스 설명.
Notifier 클래스:
- Notifier를 상속한 클래스(UserViewModel)는 상태를 가지고 있으며, 이 상태를 업데이트하면 UI가 리빌드됩니다.
- build 메서드에서 초기 상태를 설정합니다.
- getUser, setUserName, setUserAuthLogin 등의 메서드를 통해 상태를 가져오거나 업데이트합니다.
NotifierProvider:
- **NotifierProvider**는 **Notifier**를 사용하여 상태를 관리하는 Provider입니다.
- UserViewModel 타입의 Notifier와 그 Notifier가 관리하는 상태인 **User**를 제공합니다.
- **() => UserViewModel()**을 통해 **UserViewModel**의 인스턴스를 생성합니다.
ConsumerStatefulWidget:
- **ConsumerStatefulWidget**은 **ref**를 사용하여 Provider를 관찰하고 해당 Provider의 상태를 사용하여 UI를 리빌드하는 StatefulWidget입니다.
ref.watch:
- **ref.watch(userProvider)**를 통해 Provider를 관찰하고, 해당 Provider의 상태에 따라 UI가 리빌드됩니다.
ref.read(userProvider.notifier):
- **ref.read**를 통해 Provider의 notifier를 읽어오고, 이를 통해 setUserName 메서드와 같은 메서드를 호출하여 상태를 업데이트합니다.
이렇게 **NotifierProvider**를 사용하면 상태 관리와 UI 간의 효율적인 상호작용을 달성할 수 있습니다. 상태가 업데이트될 때마다 UI가 자동으로 업데이트되어 쉽게 반응형 UI를 구현할 수 있습니다.
NotifierProvider로 상태관리하기 때문에 화면을 나갔다가 들어와도 값이 관리 됩니다.
AsyncNotifierProvider 예시.
AsyncNotifierProvider를 사용하면 loading:, error:, data: 으로 state 상태에 따라 보여줄 위젯을 지정하여 return 할 수 있습니다.
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
//Model
class User{
User({required this.name,required this.autoLogin});
final String name;
final bool autoLogin;
}
//ViewModel
class UserViewModel extends AsyncNotifier<User> {
//비동기적으로 Notifier를 사용하기위해 AsyncNotifier을 상속
@override
FutureOr<User> build() async{
final user = User(name: "name", autoLogin: false);
return user;
//FutureOr로 User 초기화
}
Future<User> getUser() async{
final user = User(name: state.value!.name, autoLogin: state.value!.autoLogin);
return user;
//User get 메소드
}
Future<void> setUserName(value)async{
state = AsyncValue.loading();
//state를 로딩상태로 만들어줌
await Future.delayed(Duration(seconds: 2));
//Future.delayred로 강제로 2초간 딜레이를 줌(Loading되는 예시를 구현하기 위함)
state = AsyncValue.data(User(name: value, autoLogin: state.value!.autoLogin));
//state에 data를 넣어줌 (로딩완료) set 매소드
}
Future<void> setUserAutoLogin(value) async{
state = AsyncValue.data(User(name: state.value!.name, autoLogin: value));
}
}
final userProvider = AsyncNotifierProvider<UserViewModel,User >(()=> UserViewModel());
//AsyncNotifierProvider 생성
//View
class UserViewPage extends ConsumerStatefulWidget {
const UserViewPage({super.key});
@override
UserViewPageState createState() => UserViewPageState();
}
class UserViewPageState extends ConsumerState<UserViewPage> {
TextEditingController _editingController = TextEditingController();
//텍스트 입력창에 입력된 Text를 이용하기 위해 컨트롤러 생성
@override
void initState() {
_editingController.addListener(() { });
//컨트롤러 초기화
super.initState();
}
@override
void dispose() {
_editingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Container(
color: Colors.amber.shade800,
height: 300,
width: 300,
child:
ref.watch(userProvider).when(
//AsyncNotifierProvider 사용으로 when 기능 작성
loading: () => CircularProgressIndicator(),
//loading: state가 로딩 상태일 때 보여줄 위젯을 작성하여 return함
error: (error, stackTrace) => Text("${error}"),
//error: state가 로딩 상태일 때 보여줄 위젯을 작성하여 return함
data: (data) => Column(
//data: state가 데이터를 받았을 때 보여줄 위젯을 작성하여 return함
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(ref.watch(userProvider).value!.name,
style: TextStyle(fontSize: 40),),
TextField(controller: _editingController,cursorWidth: 2,style: TextStyle(fontSize: 20),),
SwitchListTile.adaptive(
title: Text("autoLogin"),
value: ref.watch(userProvider).value!.autoLogin,
onChanged: (value) =>
ref.read(userProvider.notifier).setUserAutoLogin(value),),
CupertinoButton(child: Text("이름 변경"),
onPressed:()=>
ref.read(userProvider.notifier).setUserName(_editingController.text))
],),)
),),
);
}
}
주요 메서드, 소스 설명.
AsyncValue:
**AsyncValue**는 Riverpod에서 비동기적인 상태를 나타내는 클래스입니다. 이 클래스는 세 가지 상태를 가집니다.
- AsyncValue.data(value): 비동기 작업이 성공적으로 완료되었고, 결과 데이터 **value**를 가지고 있는 상태입니다.
- AsyncValue.loading(): 현재 비동기 작업이 진행 중이며, 데이터를 아직 수신하지 못한 상태입니다.
- AsyncValue.error(error, stackTrace): 비동기 작업이 에러로 인해 실패하였고, 에러 정보 **error**와 해당 에러의 스택 트레이스 **stackTrace**를 가지고 있는 상태입니다.
when 메소드:
when 메소드는 **AsyncValue**의 상태에 따라 다른 작업을 수행할 수 있도록 도와주는 메소드입니다. 주로 UI에서 상태에 따라 다른 위젯이나 작업을 표시하는 데 사용됩니다.
예를 들어, 다음 코드에서 **ref.watch(userProvider).when(...)**은 **AsyncValue**의 현재 상태에 따라 다른 동작을 수행합니다.
ref.watch(userProvider).when(
loading: () => CircularProgressIndicator(),
error: (error, stackTrace) => Text("${error}"),
data: (data) => Column(
children: [
Text(data.name, style: TextStyle(fontSize: 40)),
// 나머지 UI 요소들...
],
),
);
이 코드는 상태가 로딩 중이면 **CircularProgressIndicator**를, 에러가 발생하면 에러 메시지를, 데이터가 있다면 데이터를 표시하는 Column을 반환합니다. 이렇게 하면 상태에 따라 다른 UI를 효과적으로 처리할 수 있습니다.
AsyncNotifierProvider로 완성된 화면
'앱 개발 > Flutter' 카테고리의 다른 글
50. [Flutter] Firebase Authentication (깃허브) 인증 구현하기 (1) | 2023.12.07 |
---|---|
49. [Flutter] Firebase Authentication (이메일/비밀번호) 인증 구현하기 (2) | 2023.12.01 |
47. [Flutter] MVVM + Repository 적용, 상태관리(with Riverpod, SharedPreferences 패키지) (0) | 2023.11.17 |
46. [Flutter] Provider 상태관리 (0) | 2023.11.15 |
45. [Flutter] 앱 전체에 데이터 공유 및 수정하기, 상태 관리(with ChangeNotifier, ValueNotifier) (0) | 2023.11.09 |