복습
1. MVVM : Model + View + ViewModel
1) 모델: 데이터를 서버에서 가지고 오는 계층, 데이터 클래스, 각 모델 별로 repository 가지고 와서 json을 객체로 변환해서 반환
2) 뷰: 화면 구현하는 계층
3) 뷰모델: 모델 계층(레파지토리)에서 데이터(모델 클래스) 가지고 와서 가공하는 계층
- StatefulWidget에서 많은 역할을 수행하면 코드가 지저분해짐.
- 동작원리
ViewModel 구독하여 변경되는지 확인
-> 로직처리
-> 데이터 요청해서 받음
-> 받은 데이터 가공, 상태 업데이트
-> 자신의 상태가 바뀌었다고 알림
-> 뷰가 뷰모델을 구독하고 있으므로 상태 바뀌었다는 것을 감지하여 화면 업데이트
- Flutter에서 RiverPod이라는 상태 관리 라이브러리 사용하여 MVVM 구현
- RiverPod: 상태관리 도와주는 라이브러리
flutter pub get flutter_riverpod
터미널에서 위의 코드 입력하여 설치
구현예제)
(뷰 먼저 구현하고 마지막에 수정)
M 데이터 담을 모델 클래스 구현
class User {
User({
required this.name,
required this.age,
});
String name;
int age;
User.fromJson(Map<String, dynamic> map)
: this(
name: map['name'],
age: map['age'],
);
Map<String, dynamic> toJson() {
return {
"name": name,
"age": age,
};
}
}
데이터 가져와서 클래스로 변환(repository)
import 'dart:convert';
import 'package:flutter_state_exmaple/user.dart';
class UserRepository {
// 서버와 통신할 때에는 항상 비동기!
Future<User> getUser() async {
// 서버에서 데이터를 받아오는 데 시간이 걸리므로, 기다렸다가 다음 줄을 실행
// 여기서는 서버에서 응답받는 시간을 1초로 가정해 Future.delayed를 사용합니다.
await Future.delayed(const Duration(seconds: 1));
// 서버에서 받은 데이터라고 가정한 JSON 문자열
String serverResponse = """
{
"name": "이지원",
"age": 20
}
""";
// 서버에서 받은 JSON 형식의 문자열 데이터를 Map으로 변환
Map<String, dynamic> map = jsonDecode(serverResponse);
// Map으로 변환한 데이터를 User 객체로 변환
User user = User.fromJson(map);
// 변환된 User 객체를 반환합니다
return user;
}
}
VM 홈뷰모델
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_state_exmaple/user.dart';
import 'package:flutter_state_exmaple/user_repository.dart';
// 1. HomePage에서 사용하는 상태 클래스 정의
class HomeState {
HomeState({
required this.user,
required this.fetchTime,
});
// 유저 정보를 가지고 오지 않았을 때에는 User가 null!
User? user;
// 데이터를 가지고 온 시간. 마찬가치로 초기에는 null
DateTime? fetchTime;
}
// 2. ViewModel 구현 Notifier를 상속
// Notifier : 상태를 관리(저장, 업데이트)하고
// 업데이트 시 구독하고 있는 위젯에 변경이 되었다고 알려주는 역할
// Notifier 상속 시 이 ViewModel 이 어떤 상태를 관리할 지 제너릭으로 명시
class HomeViewModel extends Notifier<HomeState> {
// 3. build 함수 : ViewModel의 최초 상태를 초기화
@override
HomeState build() {
return HomeState(
user: null, // 초기 상태 null
fetchTime: null,
);
}
// 4. 유저 정보 UserRepository에서 가져와서 상태 업데이트 하는 로직 구현
void getUserInfo() async {
UserRepository userRepository = UserRepository();
User user = await userRepository.getUser();
// 이렇게 사용하면 안됨. Notifier 클래스는 새로운 상태 객체를 사용해야 위젯에게 알려줌
// state.user = user;
// state.fetchTime = DateTime.now();
// 이렇게 새로운 객체를 상테에 할당!
state = HomeState(
user: user,
fetchTime: DateTime.now(),
);
}
}
// 5. RiverPod은 ViewModel을 위젯에서 직접 생성자 호출(HomeViewModel())해서 사용하는게 아니라
// 자체적으로 관리를해줌.
// HomeViewModel 을 A라는 위젯에서 처음 사용하면 새로운 HomeViewModel 생성
// => 여기서 B라는 위젯에서 HomeViewModel 을 사용할 때 riverpod Provider가 기존에 생성된 HomeViewModel을 돌려줌
// 사용법 : NotifierProvider 클래스를 이용해 HomeVideModel 제공
// NotifierProvider 상속받을 때 ViewModel 클래스 타입, ViewModel에서 관리하는 상태의 클래스 타입 명시
final homeViewModelProvider = NotifierProvider<HomeViewModel, HomeState>(() {
return HomeViewModel();
});
V 홈페이지
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_state_exmaple/home_view_model.dart';
void main() {
// 이 앱에서 ViewModel을 RiverPod이 관리하게 해주게 해줌
runApp(const 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) {
return Scaffold(
appBar: AppBar(),
// ViewModel에 접근할 때 Consumer라는 위젯 사용
body: Consumer(
// 그 이유가 Consumer의 Builder 속성에서
// WidgetRef를 전달해주는데
// WidgetRef가 Notifier, 즉 ViewModel에 접근할 수 있게 해줌
builder: (context, ref, child) {
// ViewModel이 관리하는 상태에 접근 :
// ref.watch에 ViewModelProvider를 넣어주면 ViewModel의 상태를 반환해주고
// ViewModel이 업데이트 될 때마다 Consumer 위젯의 builder 가 재호출되어서 새로 그려짐
HomeState homeState = ref.watch(homeViewModelProvider);
// ref.read 함수도 있는데 read함수로 상태를 가지고 오면 업데이트 되어도 Consumer builder 재호출 안됨
// HomeState homeState = ref.read(homeViewModelProvider);
return Column(
children: [
Text('이름: ${homeState.user?.name ?? ""}'),
Text('나이: ${homeState.user?.name ?? ""}'),
Text('데이터 가져온 시간 : ${homeState.fetchTime ?? ""}'),
GestureDetector(
onTap: () {
// ViewModel에 직접 접근할 때는 homeViewModelProvider.notifier 를 넣어주어서 접근
// ViewModel 자체는 업데이트 되지 않기 때문에 read 사용
HomeViewModel homeViewModel =
ref.read(homeViewModelProvider.notifier);
homeViewModel.getUserInfo();
print("클릭!");
},
child: Text('정보 가져오기'),
),
],
);
},
),
);
}
}
뷰 모델 구현하기
순서:
1. 상태 클래스 만들기
2. 뷰모델 만들기
3. 뷰모델 관리자 만들기
뷰 모델에 접근할 때 Consumer라는 위젯 사용
:Consumer의 빌더 속성에서 위젯Ref를 전달해주는데 위젯ref가 뷰모델에 접근할 수 있게 해 준다.
통신 시 사용법 총정리
- RiverPod 패키지 추가 flutter pub add flutter_riverpod
- main 함수에서 최상위 위젯을 ProviderScope 로 감싸기
- Widget 구현
- 데이터를 담을 Model 클래스 만들기
- 모델 가져오는 Repository 클래스 만들기
- Widget 에서 관리될 상태 클래스 만들기
- 상태를 관리할 ViewModel 만들기(Notifier 상속)
- 최초 상태 build 메서드 재정의 해서 설정
- Repotory에서 데이터 가져와서 상태 업데이트 하는 함수 작성
- ViewModel을 공급할 viewModelProvider(NotifierProvider 객체) 만들기
- Widget 에서 Consumer 위젯 사용해서 데이터 씌우기 및 함수 연결
- 상태 업데이트 될 때 위젯이 변경되길 원하면 ref.watch
- 한번만 값을 받아오고 싶으면 ref.read
- ViewModel의 메서드를 사용하고 싶으면 ref.read 에 viewModelProvider.notifier 넘겨주기
'TIL' 카테고리의 다른 글
[TIL] Bottom Navigator Bar (0) | 2024.12.09 |
---|---|
[트러블슈팅] 지역 검색 앱 (0) | 2024.12.06 |
[TIL] Swagger (0) | 2024.12.04 |
[TIL] REST API (0) | 2024.12.03 |
[TIL] market app 만들기 (0) | 2024.12.02 |