Post

플러터에서 Provider와 Service 비교

플러터에서 Provider와 Service 비교

Flutter로 앱을 개발할때 provider와 service의 역할이 헷갈렸기 때문에 각각에 대해서 정리하고 비교한다

  • Provider는 주로 상태 관리UI 업데이트에 초점
  • Service비즈니스 로직, 데이터 처리, 네트워크/API 통신 등 UI와 직접 관계가 없는 작업

 

Provider의 정의 및 역할

정의: Provider는 Flutter의 상태 관리 솔루션으로, ChangeNotifier를 확장하여 앱의 상태를 중앙에서 관리하고, 해당 상태 변화에 따라 UI를 자동으로 업데이트한다.

주요 역할:

  • 애플리케이션의 상태(데이터)를 저장 및 관리
  • UI 위젯에 상태를 공급하여, 상태 변화 시 자동으로 리렌더링
  • 의존성 주입(DI)을 통해 앱의 여러 부분에서 동일한 상태에 접근 가능

사용:

  • 사용자 정보, 테마 설정, 네비게이션 상태 등 전역 상태 관리
  • 특정 페이지나 위젯 트리 내에서 로컬 상태 관리

 

 

Service의 정의 및 역할

정의: Service는 애플리케이션의 비즈니스 로직, 데이터 처리, 네트워크/API 호출 등 UI와 직접 관련 없는 작업을 수행하는 모듈 또는 클래스

주요 역할:

  • 외부 API 통신 및 데이터베이스 접근
  • 데이터 전처리, 캐싱, 비즈니스 로직 실행
  • 서비스 로직을 캡슐화하여 UI와 분리

사용:

  • 로그인, 토큰 갱신, API 데이터 요청 등 외부 시스템과의 통신
  • 데이터 가공 및 비즈니스 로직 처리
  • 로컬 데이터 저장 및 캐싱

 

 

역할 분리의 필요성

  • 책임 분리(Single Responsibility Principle):
    • Provider: 앱 상태 및 UI 관련 데이터를 관리
    • Service: 데이터 처리 및 외부 API 통신 로직을 담당
  • 유지보수성:
    • 서비스 로직 변경 시 UI 코드를 수정할 필요 없이, Service 파일만 업데이트하면 됨.
    • Provider는 UI 상태 관리에 집중하여, 각 컴포넌트의 역할이 명확해짐

 

상호 작용 방식

  1. Service → Provider:
    • Service에서 API 호출을 수행하고, 그 결과 데이터를 Provider에 전달하여 상태를 업데이트
    • 예를 들어, AuthService는 로그인 API를 호출하고, 로그인 성공 시 받은 유저 정보를 UserProvider에 저장 (내가 만든 flutter앱의 예시)
  2. Provider → Service:
    • Provider는 필요 시 Service의 메서드를 호출하여 데이터를 가져오거나, 토큰 갱신 등 추가 작업을 수행
    • 예를 들어, UserProviderfetchUser() 메서드는 API 호출 전에 Service를 통해 토큰을 갱신(내가 만든 flutter앱의 예시)
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//service
import 'package:kakao_flutter_sdk/kakao_flutter_sdk_user.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:dio/dio.dart';
import '../models/user_model.dart';
import '../providers/user_provider.dart';

class AuthService {
  final Dio _dio = Dio();
  final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();

  Future<void> login(UserProvider userProvider) async {
    try {
      OAuthToken token;
      if (await isKakaoTalkInstalled()) {
        token = await UserApi.instance.loginWithKakaoTalk();
      } else {
        token = await UserApi.instance.loginWithKakaoAccount();
      }
      print('카카오 로그인 성공: ${token.accessToken}');

      final response = await _dio.post(
        'https://api.mapping.kro.kr/api/v2/member/login',
        options: Options(
          headers: {'accept': '*/*', 'Content-Type': 'application/json'},
        ),
        data: {'accessToken': token.accessToken},
      );

      if (response.statusCode == 200 && response.data['success']) {
        final data = response.data['data'];
        String newAccessToken = data['tokens']['accessToken'];
        String refreshToken = data['tokens']['refreshToken'];

        await _secureStorage.write(key: 'accessToken', value: newAccessToken);
        await _secureStorage.write(key: 'refreshToken', value: refreshToken);

        print('서버 로그인 성공, 토큰 저장 완료');
        await userProvider.fetchUser();
      } else {
        print('서버 로그인 실패: ${response.data}');
      }
    } catch (error) {
      print('카카오 로그인 실패: $error');
    }
  }

  Future<void> logout(UserProvider userProvider) async {
    try {
      await _secureStorage.delete(key: 'accessToken');
      await _secureStorage.delete(key: 'refreshToken');
      userProvider.clearUser();
      print('로그아웃 완료');
    } catch (error) {
      print('로그아웃 실패: $error');
    }
  }
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//provider
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:dio/dio.dart';
import '../models/user_model.dart';

class UserProvider with ChangeNotifier {
  UserModel? _user;
  final Dio _dio = Dio();
  final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();

  UserModel? get user => _user;

  void setUser(UserModel user) {
    _user = user;
    notifyListeners();
  }

  void clearUser() {
    _user = null;
    notifyListeners();
  }
}

This post is licensed under CC BY 4.0 by the author.