일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- AnimationController
- 오류
- db
- MSSQL
- Animation
- 애니메이션
- 플러터
- Flutter
- 마우이
- 바인딩
- 깃허브
- 리엑트
- .NET
- MVVM
- 파이어베이스
- Maui
- typescript
- 자바스크립트
- page
- spring boot
- Binding
- 닷넷
- 함수
- MS-SQL
- listview
- Firebase
- React JS
- JavaScript
- GitHub
- HTML
- Today
- Total
개발노트
47. [Flutter] MVVM + Repository 적용, 상태관리(with Riverpod, SharedPreferences 패키지) 본문
47. [Flutter] MVVM + Repository 적용, 상태관리(with Riverpod, SharedPreferences 패키지)
mroh1226 2023. 11. 17. 15:47MVVM 패턴
MVVM(Mode-View-ViewModel)은 소프트웨어 아키텍처 패턴 중 하나로, 주로 사용자 인터페이스를 구축하는 데 적합한 패턴입니다.
- Model: 데이터와 비즈니스 로직을 담당하는 부분.
- View: 사용자 인터페이스를 담당하는 부분.
- ViewModel: View와 Model 간의 중간자로, 상태 관리 및 비즈니스 로직을 처리.
MVVM 패턴은 몇 가지 주요 장점이 있어 많은 개발자들이 선호하는데, 이를 자세히 설명해보겠습니다.
- 분리된 역할(Role Separation):
MVVM은 각 구성 요소가 명확하게 분리되어 있습니다. Model은 데이터와 비즈니스 로직을 처리하고, View는 사용자 인터페이스를 담당하며, ViewModel은 View와 Model 간의 통신을 중개합니다. 이로써 코드가 각 부분에 집중되어 유지 보수와 테스트가 쉬워집니다. - 유연성(Flexibility):
MVVM은 느슨한 결합(Loose Coupling)을 촉진합니다. 각 구성 요소는 서로에게 독립적이며, 변경이 발생해도 다른 부분에 미치는 영향을 최소화합니다. 이로써 새로운 기능 추가나 기존 기능의 변경이 간편합니다. - 테스트 용이성(Testability):
ViewModel은 비즈니스 로직을 캡슐화하고, 이로써 ViewModel을 테스트하기 쉬워집니다. - 재사용성(Reusability):
ViewModel은 UI와 분리되어 있기 때문에, 여러 화면에서 재사용이 가능합니다. 동일한 ViewModel을 여러 View에서 사용할 수 있어 개발 생산성을 높이고 중복 코드를 최소화합니다. - 데이터 바인딩(Data Binding):
MVVM은 데이터 바인딩을 통해 View와 ViewModel 사이의 데이터 동기화를 쉽게 구현할 수 있습니다. 이로써 사용자 인터페이스 업데이트 및 사용자 입력 처리가 간편해지며, 코드의 가독성이 향상됩니다. - 비즈니스 로직 집중(Business Logic Focus):
MVVM은 비즈니스 로직을 ViewModel에 중점을 두어 관리합니다. 이로써 UI 코드와 비즈니스 로직이 혼재되지 않고, 코드의 가독성이 높아집니다. - 반응형(Reactive) 프로그래밍 지원:
MVVM 패턴은 반응형 프로그래밍과 쉽게 통합될 수 있습니다. 데이터 바인딩과 같은 기술을 사용하여 데이터의 변화에 반응하고 UI를 업데이트할 수 있습니다.
Repository
Repository 패턴은 데이터 소스와 애플리케이션 간의 중재자 역할을 하는 디자인 패턴입니다. 애플리케이션은 Repository를 통해 데이터에 접근하며, 데이터의 원본(로컬Local, 원격 Remote 등)에 대한 구체적인 구현은 Repository에서 캡슐화됩니다.
시작전 패키지 설치.
Riverpod
Riverpod은 Provider 패키지의 확장으로, Flutter 애플리케이션에서 상태 관리를 용이하게 할 수 있는 라이브러리입니다. NotifierProvider를 사용하여 상태를 효과적으로 관리할 수 있습니다.
pubdev Riverpod 설치 링크: https://pub.dev/packages/flutter_riverpod/install
SharedPreference
SharedPreferences는 Flutter 애플리케이션에서 간단한 키-값 쌍의 형태로, 기기 내의 저장소를 사용하여 데이터를 지속적으로 저장하고 검색하는 데 사용되는 패키지입니다. 앱의 설정, 사용자 기본 설정 등을 저장할 수 있습니다.
pubdev SharedPreferences 설치 링크: https://pub.dev/packages/shared_preferences
MVVM + Repository 폴더 구조
/lib
|-- /data
| |-- repository.dart
|-- /models
| |-- user.dart
|-- /viewmodels
| |-- user_viewmodel.dart
|-- /views
| |-- home_screen.dart
|-- main.dart
MVVM + Repository 예시 소스.
내용: 사용자 (User)가 이메일을 받는지(receiveEmail) Push알람을 받는지(receivePush) 여부를 상태관리하는 예시
Model
models/user.dart
//Model class User { final bool receiveEmail; //이메일 받는지 여부 final bool receivePush; //Push 알람 받는지 여부 User({required this.receiveEmail, required this.receivePush}); }
- Entity와 생성자를 선언하여, 사용할 Data의 기본 형태를 만들어줍니다.
- Model에 기본 Data가 될 User 객체는 Bool형의 receiveEmail 과 receivePush 를 갖습니다.
Repository
data/repository.dart
//Repository class UserRepository { final SharedPreferences _preferences; UserRepository(this._preferences); //기기에 데이터 저장하기 위해 SharedPreferences 패키지 사용 //Repository를 생성할 때, SharedPreferences 를 인자로 받도록함(생성자) User? getUser() { final receiveEmail = _preferences.getBool('receiveEmail'); final receivePush = _preferences.getBool('receivePush'); //SharedPreferences.getString()으로 기기에 저장된 key의 value를 가져옴 if (receiveEmail == null || receivePush == null) { return User(receiveEmail: false, receivePush: false); } return User(receiveEmail: receiveEmail, receivePush: receivePush); //값이 없으면 return null, 있으면 User에 값을 채워 return함 } Future<void> setReceiveEmail(bool value) async { await _preferences.setBool('receiveEmail', value); //SharedPreferences.setString()으로 key 값을 기기에 저장함 } Future<void> setReceivePush(bool value) async { await _preferences.setBool('receivePush', value); } }
- Repository 에는 SharedPreference를 사용하여 기기에 저장된 데이터를
getBool('key이름') 불러오고, setBool('key이름')저장 할 수 있는 기능들을 구현합니다. - getUser(): 기기에 저장된 'receiveEmail', 'receivePush' 값을 가져옴
- key 값이 아직 없을 경우를 위해 if 문으로 예외처리해줍니다.
- setReceiveEmail(bool value): 기기에 저장된 key 'receiveEmail' 에 value 값을 저장함
- setReceivePush(bool value): 기기에 저장된 key 'receivePush' 에 value 값을 저장함
ViewModel
viewmodels/user_viewmodel.dart
class UserViewModel extends Notifier<User> { final UserRepository _repository; UserViewModel(this._repository); //UserRepository를 인자로 받는 UserViewModel 생성 void setReceiveEmail(bool value) { _repository.setReceiveEmail(value); //Repository의 set 메소드 호출로 값을 저장함 state = User(receiveEmail: value, receivePush: state.receivePush); //state를 통해 상태를 업데이트 시키고 User의 변화를 Notifier에 알림 } void setReceivePush(bool value) { _repository.setReceivePush(value); state = User(receiveEmail: state.receiveEmail, receivePush: value); } @override User build() { // UserRepository를 통해 현재 유저 정보를 기기 저장소로 부터 가져와서 build()로 초기설정함 User? user = _repository.getUser(); return User( receiveEmail: user!.receiveEmail, receivePush: user.receivePush); } } final userViewModelProvider = NotifierProvider<UserViewModel, User>(() => throw UnimplementedError());
- ViewModel
- Notifier<>에 User를 넣어 User의 state 를 관리할 수 있도록합니다.
- view에서 사용될 메소드들 setReceiveEmail(), setReceivePush() 을 만들어주고, Repository를 통해 값을 저장하고 state로 변화를 Notifier에 알리도록 작성합니다.
- @override User build()를 사용하여 User의 초기 값을 저장소로부터 가져오도록 작성합니다.
StatefulWidget 👉🏻 ConsumerStatefulWidget 변경예시)
View
views/home_screen.dart
1. ConsumerWidget 을 사용할 경우 (StatelessWidget 과 유사)
//view class HomePage extends ConsumerWidget { const HomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( body: Column( children: [ SwitchListTile.adaptive( title: const Text('receiveEmail'), value: ref.watch(userViewModelProvider).receiveEmail, onChanged: (value) { ref.read(userViewModelProvider.notifier).setReceiveEmail(value); }, ), SwitchListTile.adaptive( title: const Text('receivePush'), value: ref.watch(userViewModelProvider).receivePush, onChanged: (value) { ref.read(userViewModelProvider.notifier).setReceivePush(value); }, ), ], ), ); } }
2. ConsumerStatefulWidget 사용할 경우(StatefulWidget 과 유사)
//view class HomePage extends ConsumerStatefulWidget { const HomePage({super.key}); @override HomePageState createState() => HomePageState(); } class HomePageState extends ConsumerState<HomePage> { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ SwitchListTile.adaptive( title: const Text('receiveEmail'), value: ref.watch(userViewModelProvider).receiveEmail, onChanged: (value) { ref.read(userViewModelProvider.notifier).setReceiveEmail(value); }, ), SwitchListTile.adaptive( title: const Text('receivePush'), value: ref.watch(userViewModelProvider).receivePush, onChanged: (value) { ref.read(userViewModelProvider.notifier).setReceivePush(value); }, ), ], ), ); } }
- NotifierProvider로 선언해주었던 userViewModelProvider로 아래와 같이 이용합니다.
- 리빌드 필요 시, ref.watch(userViewModelProvider).receiveEmail; -> ex) 변수값 사용
- 리빌드 불필요 시, ref.read(userViewModelProvider).setReceivePush(value); -> ex) 메소드 사용
위와 같이 리빌드가 필요할 경우 watch, 리빌드가 필요하지않을 경우 read를 사용합니다.
main.dart 에 NotifierProvider 추가(필수)
main.dart
void main() async { WidgetsFlutterBinding.ensureInitialized(); final preferences = await SharedPreferences.getInstance(); final repository = UserRepository(preferences); runApp(ProviderScope( overrides: [ userViewModelProvider.overrideWith(() => UserViewModel(repository)) ], child: const MyApp(), )); }
- main() 함수에 위와 같이 ProviderScope를 추가하고 overrides: [] 에 Provider를 추가합니다.
- child: 속성에 최상위 위젯을 넣어줍니다.
MVVM + Repository 형태에 Riverpod, SharedPreferences 를 적용하여 상태관리하는 모습
- receiveEmail , receivePush 로 앱을 종료하고 실행 시켜도 상태관리가 되는 모습을 볼 수 있습니다.
필기.
'앱 개발 > Flutter' 카테고리의 다른 글
49. [Flutter] Firebase Authentication (이메일/비밀번호) 인증 구현하기 (2) | 2023.12.01 |
---|---|
48. [Flutter] Riverpod Provider 종류와 사용법(with MVVM 패턴) (1) | 2023.11.24 |
46. [Flutter] Provider 상태관리 (0) | 2023.11.15 |
45. [Flutter] 앱 전체에 데이터 공유 및 수정하기, 상태 관리(with ChangeNotifier, ValueNotifier) (0) | 2023.11.09 |
44. [Flutter] InheritedWidget 로 위젯 트리에 데이터 공유하기 (0) | 2023.11.09 |