본문 바로가기
TIL

[TIL] Flutter - RiverPod 사용법

by chengzior 2024. 12. 5.

 

복습

 

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