MVVM Clean Architecture (+Provider)

2024. 11. 26. 20:31·개발노트/Flutter
반응형

Flutter 개발을 시작하며 공부했던 Clean Architecture에 관한 내용이다.

혼자 공부하면서 메모하듯이 적은 내용에 가깝다.


 

Model  
Data Layer Domain Layer Presentation Layer
Data Source

Repository
(Implements)
UseCase

Repository
(Interface)

Entity
(Model Class)
View

View Model

(이미지를 불펌하기는 좀 그렇고 계층 구분이라도 한눈에 보기 쉬우라고 표로 구역 나눠봤다.)

 

1. Usecases & Repository & DataSource

Usecases

UseCases는 애플리케이션의 비즈니스 로직을 캡슐화합니다. UseCase는 Domain 레이어에서 정의됩니다. 뷰모델은 UseCase를 호출하고 UseCase는 Repository를 사용하여 데이터를 가져옵니다. 이를 통해 뷰모델은 비즈니스 로직과 데이터 액세스를 분리할 수 있습니다.

 

Repository

Repository는 데이터를 가져오거나 저장하는 데 사용됩니다. Repository는 다른 데이터 원본, 예를 들어 로컬 데이터베이스, 원격 데이터베이스 또는 API와 상호 작용할 수 있습니다. Repository는 데이터 액세스에 대한 인터페이스를 제공하며, 실제 데이터 원본에 대한 구현 세부 정보를 숨깁니다.

 

DataSource

DataSource는 Repository에서 데이터를 가져오는 데 사용되는 실제 데이터 원본입니다. DataSource는 데이터를 검색하고, 필터링하고, 정렬하는 등의 일을 수행합니다. Repository는 다양한 DataSource를 가질 수 있으며, 이는 애플리케이션의 성능과 확장성에 큰 영향을 미칩니다.

 

→ Usecase는, 특정한 하나의 기능만을 담당한다. 예를 들면 이미지 전송, 로드, 삭제 각각의 Usecase를 만들어 뷰모델에서는 Usecase 호출만 하면 된다.

Usecase의 각 기능들은 또 Repository의 함수를 호출한다.

Repository는 다양한 기능의 함수들이 모여있다. 이것 역시 Data Source의 함수를 호출한다.

마지막으로 Date Source에서는 직접적으로 API나 파이어베이스에 접근하는 메서드를 작성하면 된다

ViewModel {
	CreateUsecase
	LoadUsecase
	DeleteUsecase
}

CreateUsecase {
	repository.create
}
LoadUsecase {
	repository.load
}
DeleteUsecase {
	repository.delete
}

Repository {
	datasource.create
	datasource.load
	datasource.delete
}

DataSource {
	create
	load
	delete
} // 실제 데이터 통신

이런 형태가 된다는 것 같다.

 

 

2. 근데..레포지토리 단계는 굳이 필요한가?

데이터 소스의 메소드들을 레포지토리에 넣으면 안돼?

추상화와 모듈성: 데이터 원본에 대한 구현 세부정보를 숨기면, 다른 부분에서 이를 참조할 때 추상화된 인터페이스만 사용하면 됩니다. 이는 코드의 모듈성을 높여줍니다. 또한, 추상화된 인터페이스를 이용해 다른 데이터 원본으로의 교체가 용이해집니다.
보안: 실제 데이터 원본에 대한 구현 세부정보를 외부에 노출시키지 않으면, 보안상의 이슈가 줄어듭니다.
단일책임의 원칙: Repository는 데이터 원본에서 데이터를 가져오는 역할만 담당합니다. 따라서, 이를 구현하는 세부적인 부분은 Repository의 책임이 아닙니다. 이를 구현하는 세부적인 부분은 DataSource에서 처리하게 되므로, 코드의 단일책임의 원칙을 지킬 수 있습니다.

 

추상 클래스는 굳이 왜 써야하지?

추상 클래스를 사용하는 이유 중 하나는 다형성을 구현하기 위해서입니다. 즉, 여러 클래스가 동일한 기능을 수행하지만 세부적인 구현 방법이 다를 때, 추상 클래스를 상속하여 기능을 구현하면 코드의 재사용성과 유지보수성을 높일 수 있습니다.
예를 들어, 여러 데이터 소스가 있을 때 (예: 로컬 데이터베이스, 원격 서버, 캐시 등), 이 데이터 소스들은 같은 데이터를 다루지만, 각각의 데이터 소스는 그 데이터를 다루는 방식이 조금씩 다릅니다. 추상 클래스를 사용하여 이러한 데이터 소스들이 동일한 메서드를 구현하도록 강제하고, 실제 데이터 소스 클래스에서 추상 클래스를 상속하여 구체적인 기능을 구현하면 코드의 일관성과 가독성을 유지할 수 있습니다.

 

→ FirebaseDataSource, LocalServerDataSource 같은 걸 만들어도 같은 형식의 클래스로 함수 모양만 달라지게 해서 사용할 수 있음.

= 테스트가 용이해진다.

 

 

3. Provider의 역할과 사용법

3-a. Ephemeral state

Ephemeral state는 임시적인 상태라는 뜻이다. Local state와 의미가 같다. Local state는 조금 더 이해하기가 쉽다. 한 개의 위젯이 가지는 상태를 Ephemeral state라고 한다. 가령,

  • 현재 PageView
  • 현재 진행 중인 애니메이션
  • BottomNavigationBard의 현재 탭

을 일컫는다. 이러한 경우에는 상태 관리법을 사용하는 게 필요 없다. 조금 더 단순하게 생각을 하자면, 현재 페이지에서만 사용하는 상태이며, 다른 곳에서는 영향을 주지 않는 경우(Local)를 말한다. 그래서 이러한 경우에는 상태 관리가 크게 필요하지 않다.

하지만 앱 전체에 영향을 주는 경우에는 상태 관리를 해야 한다는 뜻이다.

 

3-b. App state

App state는 어떠한 상태가 앱 전체에 영향을 주는 경우를 말한다. 예를 들어,

  • 로그인 정보
  • 유저의 설정
  • 쿠팡의 장바구니
  • 인스타그램 혹은 페이스북의 알림
  • 네이버 혹은 구글의 메일 읽음과 읽지 않음 처리

→ 여기서 Provider로 3-b 상태를 효과적으로 관리할 수 있다.

 

3-c. Provider 사용법

같은 스크린단에서 watch를 쓰게 되면, 자기가 보고 있는 value가 아닌 다른 value가 업데이트 되어도 같이 rebuild 된다.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].

void main() {
  runApp(
    /// Providers are above [MyApp] instead of inside it, so that tests
    /// can use [MyApp] while mocking the providers
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: const MyApp(),
    ),
  );
}

/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  /// Makes `Counter` readable inside the devtools by listing all of its properties
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(IntProperty('count', count));
  }
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Example'),
      ),
      body: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),

            /// Extracted as a separate widget for performance optimization.
            /// As a separate widget, it will rebuild independently from [MyHomePage].
            ///
            /// This is totally optional (and rarely needed).
            /// Similarly, we could also use [Consumer] or [Selector].
            Count(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: const Key('increment_floatingActionButton'),

        /// Calls `context.read` instead of `context.watch` so that it does not rebuild
        /// when [Counter] changes.
        onPressed: () => context.read<Counter>().increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class Count extends StatelessWidget {
  const Count({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      /// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
      '${context.watch<Counter>().count}',
      key: const Key('counterState'),
      style: Theme.of(context).textTheme.headlineMedium,
    );
  }
}

 

 

 


<도움 받은 링크들>

  • Flutter MVVM Architecture
  • MVVM Pattern in Flutter 2 - Domain, Repository, DataSource
  • provider 6.1.2 Package Example
  • 8. Dart, Flutter 상태관리 그리고 Riverpod (1)

 

 

728x90
반응형
저작자표시 비영리 동일조건 (새창열림)

'개발노트 > Flutter' 카테고리의 다른 글

Flutter 특정 버전으로 업데이트 하기  (0) 2025.01.16
Flutter에서 iOS TableView Section Header 같은 UI 구현하기  (0) 2024.11.30
Flutter에서 Firebase Authentication으로 이메일 로그인 구현하기  (2) 2024.11.24
Flutter에서 로컬 DB 구현하기 (SQFlite)  (2) 2024.11.23
Flutter에서 FCM 설정하기  (1) 2024.11.21
'개발노트/Flutter' 카테고리의 다른 글
  • Flutter 특정 버전으로 업데이트 하기
  • Flutter에서 iOS TableView Section Header 같은 UI 구현하기
  • Flutter에서 Firebase Authentication으로 이메일 로그인 구현하기
  • Flutter에서 로컬 DB 구현하기 (SQFlite)
Jeck Lee
Jeck Lee
Android까지의 정복을 노리고 있는 iOS 앱 개발자입니다
  • Jeck Lee
    쩩쩩노트
    Jeck Lee
  • 전체
    오늘
    어제
    • 분류 전체보기 (41)
      • 개발노트 (39)
        • iOS (19)
        • Android (1)
        • Flutter (15)
        • JavaScripit (1)
        • 기타 (3)
      • 자유노트 (2)
        • 이 앱 저 앱 사용기 (1)
  • 인기 글

  • 최근 글

  • 링크

    • 쩩깃
  • 태그

    Flutter
    Firebase
    sns login
    ios
    Xcode
    Google Login
    Android
    SwiftUI
    APPLE LOGIN
    Swift
  • «   2025/09   »
    일 월 화 수 목 금 토
    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
    250x250
  • hELLO· Designed By정상우.v4.10.1
Jeck Lee
MVVM Clean Architecture (+Provider)
상단으로

티스토리툴바