Search
๐Ÿ“š

EP4. Flutter Clean Architecture

์ƒ์„ฑ์ผ
2024/06/02 14:54
๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ
2024/06/02

EP4. Flutter Clean Architecture

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์„ค๊ณ„ ์›์น™์œผ๋กœ, ์ฝ”๋“œ์˜ ์œ ์—ฐ์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ๊ณ„์ธต์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์˜์กด์„ฑ์„ ์—ญ์ „์‹œํ‚ค๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์ฃผ์š” ๊ฐœ๋…์€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๊ตฌํ˜„ ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ๋ถ„๋ฆฌํ•˜์—ฌ, ์ƒ์œ„ ๊ณ„์ธต์ด ํ•˜์œ„ ๊ณ„์ธต์— ์˜์กดํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‹œ์Šคํ…œ ๋ณ€๊ฒฝ์ด ํŠน์ • ๊ณ„์ธต์—๋งŒ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋„๋ก ํ•˜์—ฌ, ์‹œ์Šคํ…œ์˜ ํ™•์žฅ์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.

1. MVVM ํŒจํ„ด

MVVM (Model-View-ViewModel) ํŒจํ„ด์€ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด์œผ๋กœ, ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค:
โ€ข Model: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ์ดํ„ฐ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
โ€ข View: ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI)๋ฅผ ๋‹ด๋‹นํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ํ™”๋ฉด ์š”์†Œ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
โ€ข ViewModel: View์™€ Model ์‚ฌ์ด์˜ ์ค‘๊ฐ„ ๊ณ„์ธต์œผ๋กœ, View์˜ ์ƒํƒœ์™€ ๋™์ž‘์„ ๊ด€๋ฆฌํ•˜๋ฉฐ Model๊ณผ์˜ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

2. ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

lib/ โ”œโ”€โ”€ entities/ ๐Ÿ‘ˆ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ๋˜๋Š” Entity class ๊ด€๋ฆฌ โ”œโ”€โ”€ services/ โ”‚ โ”œโ”€โ”€ push_service.dart/ ๐Ÿ‘ˆ ์˜ˆ์‹œ. ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜์—์„œ ์œ ์Šค์ผ€์ด์Šค ํŒŒํŠธ โ”œโ”€โ”€ views/ ๐Ÿ‘ˆ ๊ฐ ํ™”๋ฉด๋ณ„ ์ฝ”๋“œ โ”‚ โ”œโ”€โ”€ home/ ๐Ÿ‘ˆ ์˜ˆ์‹œ. page(view), viewModel, model๋กœ ๊ตฌ์„ฑ๋จ โ”‚ โ”‚ โ”œโ”€โ”€ home_page.dart โ”‚ โ”‚ โ”œโ”€โ”€ home_viewmodel.dart โ”‚ โ”‚ โ”œโ”€โ”€ home_model.dart โ”‚ โ”œโ”€โ”€ /sign in โ”‚ โ”‚ โ”œโ”€โ”€ sign_in_page.dart โ”‚ โ”‚ โ”œโ”€โ”€ sign_in_viewmodel.dart โ”‚ โ”‚ โ”œโ”€โ”€ sign_in_model.dart โ”‚ โ””โ”€โ”€ ... โ””โ”€โ”€ main.dart ๐Ÿ‘ˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ง„์ž…์ 
Plain Text
๋ณต์‚ฌ
๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ์˜ ์ •๋‹ต์€ ์—†์Šต๋‹ˆ๋‹ค. View, ViewModel, Model์„ ๊ฐ๊ฐ ๋ชจ์•„๋‘๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ ๊ฐœ์ธ์ ์œผ๋กœ ํ™”๋ฉด๋ณ„๋กœ ๋ฌถ์–ด๋‘๋Š”๊ฒŒ ๊ฐœ๋ฐœํ•˜๋Š” ๊ณผ์ •์—์„œ ํŽธ๋ฆฌํ•œ ๋ถ€๋ถ„์ด ์žˆ์–ด ์ €๋Š” ์œ„ ๊ตฌ์กฐ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

3. Service ์˜ˆ์‹œ

push_service.dart
class PushService { final ApiClient _apiClient = ApiClient('https://rkeelyzbzieohfcsmsch.supabase.co/functions/v1/api'); Future<ResponseFormat<bool>> addToken(String token) async { final body = {'fcmToken': token}; return _apiClient.post<bool>('/push/v1/fcmToken', body, (json) => json as bool); } }
Dart
๋ณต์‚ฌ
โ€ข
Service์—์„œ๋Š” ์™ธ๋ถ€๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ถ€๋กœ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

4. Model ์˜ˆ์‹œ

push_model.dart
class PushModel { final PushService _pushService; final KSNotificationService _notificationService; PushModel(this._pushService, this._notificationService); Future<ResponseFormat<bool>> addToken(String token) async { return await _pushService.addToken(token); } }
Dart
๋ณต์‚ฌ
โ€ข
Model์€ Service๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค.
โ€ข
Model์˜ ์ƒ์„ฑ์ž๋กœ Service๋ฅผ ์ฃผ์ž…๋ฐ›๋„๋ก ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.
โ€ข
Service๋กœ ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜จ ๊ฐ’์„ ๋น„์ฆˆ๋‹ˆ์Šค์—๋”ฐ๋ผ ์—ฐ์‚ฐํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

5. ViewModel ์˜ˆ์‹œ

push_view_model.dart
class PushViewModel with ChangeNotifier { final PushModel _pushModel = PushModel(PushService(), KSNotificationService()); Future<void> addToken() async { String token = tokenController.text; if (token.isEmpty) { setResponse("Token cannot be empty"); return; } final response = await _pushModel.addToken(token); if (response.result != null && response.result!) { setResponse("Token added successfully"); } else { setResponse(response.message); } } }
Dart
๋ณต์‚ฌ
โ€ข
ViewModel์€ Model์„ ๊ฐ–์Šต๋‹ˆ๋‹ค.
โ€ข
ChangeNotifier๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. View์— ๊ทธ๋ ค์งˆ State ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
โ€ข
View์— ๋ณด์—ฌ์งˆ ๊ฐ’์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. (ex. ๋‹ค๊ตญ์–ด ์ง€์›, Date Formatter, Currency Formatter)

6. View ์˜ˆ์‹œ

home_view.dart
class HomeView extends StatelessWidget { const HomeView({super.key}); Widget build(BuildContext context) { var viewModel = Provider.of<HomeViewModel>(context); return Scaffold( body: Center( child: ElevatedButton( child: Text('${viewModel.userID()} sign out'), onPressed: () { viewModel.signOut(); }, ), ), ); } }
Dart
๋ณต์‚ฌ
โ€ข
View์˜ State๋ฅผ ViewModel๋กœ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ View๋Š” StatelessWidget์œผ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

7. ๊ธฐ์กด ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๋ณ€๊ฒฝํ•˜๊ธฐ

ํ˜„์žฌ ์ƒ˜ํ”Œ์•ฑ์„ ์œ„ MVVM ํŒจํ„ด ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๋กœ ๋ณ€๊ฒฝํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
1.
์ƒ˜ํ”Œ์•ฑ ์ˆ˜์ค€์˜ Pubspec์— Provider ์ถ”๊ฐ€
flutter pub add provider flutter pub get
Dart
๋ณต์‚ฌ
ChangeNotifier๋ฅผ ์ƒ์†๋ฐ›์€ ViewModel์„ View์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ Provider๋ฅผ ์ด์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ƒ˜ํ”Œ์•ฑ ์ˆ˜์ค€์˜ Pubspec์— provider๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.
2.
ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ ๋ณ€๊ฒฝ
์•„๋ž˜ ๊ฐ•์˜ ์ฝ”๋“œ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

๊ฐ•์˜ ์ฝ”๋“œ

2
pull