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.
ํ๋ก์ ํธ ์ฝ๋ ๋ณ๊ฒฝ
์๋ ๊ฐ์ ์ฝ๋ ์ฐธ๊ณ ํด์ฃผ์ธ์.