Structured data
- Category
- Programming
플러터(Flutter) #
플러터란 #
MVC 디자인 패턴 #
- 디자인 패턴을 사용해야하는 이유
- Model, View, Controller 역할
- 실제 예시
- API 부분
- 정리
MVC 디자인 패턴을 왜 사용해야 할까? #
- MVC 패턴, 여기서 파생된 MVVM 패턴은 인터페이스, 데이터, 데이터 제어에 사용되는 소프트웨어 디자인 패턴이다. Model, View Model, View는 각자의 역할을 하며(관심사 분리) 서로 역할을 분리하며 팀 단위 프로젝트에 큰 도움이 된다.
- 한 페이지 안에 변수, 함수, 위젯을 한꺼번에 선언하면 어떻게 될까? 아마 그 UI에 해당하는 dart 파일에 들어가서 처음부터 끝까지 읽으며 변수를 변경하거나 함수를 수정할 것이다. 만약 해당 뷰가 내가 작성한 코드가 아니라면? 아마 수정할 때마다 코드를 읽어야 해서 많은 시간을 잡아먹을 것이다.
- 하지만 Model에 앱에 필요한 내용을 저장하면 해당 부분만 가서 변경할 수 있다. 코드를 전부 읽지 않아도 특정 위치에 변경 지점이 있다는 것을 인지하고 그 부분에 접근하면 시간 단축을 할 수 있을 것이다. 이제 각 역할이 무엇인지 알아보자.
Model, View, Controller 역할 #
- Model은 데이터를 저장하는 클래스이다. 대부분 앱은 프로필에 내 정보 수정 부분이 있다. 여기서 Model에 해당하는 부분은 제목, 부제목, 라벨에 해당하는 부분이다. (ex: 계정 정보, 아이디, 변경할 비밀번호)
- Controller 즉 ViewModel은 앱에서 사용자 입력(비밀번호 입력 등)에 대한 응답으로 Model의 데이터를 업데이트한다.
- View는 말 그대로 앱의 데이터를 보여주는 UI이다.

실제 예시 #
- 위 사진에 해당하는 부분을 나눈 코드의 일부이다. 먼저 Model부터 살펴보겠다. 이 화면에 해당하는 Model 부분의 패키지 이름은 profile_adjustment_model.dart이다. 나는 모델의 네이밍 규칙을 두 가지로 나뉘었다. 변하지 않는 데이터를 담는 부분은 '_model.dart'로 끝나고 변경되는 데이터를 담는 부분은 '_data_model.dart'로 구성하였다.
- profile_adjustment_model.dart의 코드를 살펴보자.
class ProfileAdjustmentModel {
final String adjustmentTitle;
final String idInfoTitle;
final String id;
final String changePassword;
final String confirmChangePassword;
final String commonInfo;
final String name;
final String birth;
final List<String> birthUnit;
final String phoneNumber;
final String confirmButton;
final Iconoir clockIconPath;
final String phoneNumberHint;
final String addressInfo;
final String homeAddress;
final String homePhone;
final String cancelButton;
final String buttonTitle;
ProfileAdjustmentModel({this.adjustmentTitle = '내 정보 수정',
this.idInfoTitle = '계정 정보',
this.id = '아이디',
this.changePassword = '변경할 비밀번호',
this.confirmChangePassword = '비밀번호 확인',
this.commonInfo = '기본 정보',
this.name = '이름',
this.birth = '생년월일',
this.cancelButton = 'assets/icon/cancel.png',
this.phoneNumber = '휴대폰 번호',
this.confirmButton = '확인',
this.phoneNumberHint = '인증번호를 입력해주세요.',
this.addressInfo = '주소 정보',
this.homeAddress = '집 주소',
this.homePhone = '집 전화번호',
this.buttonTitle='수정 완료'})
:birthUnit = ['년', '월', '일'],
clockIconPath=const Iconoir(Iconoir.alarm, width: 16, height: 16,);
}
- final 상수로 설정하였고 선언과 동시에 값을 받도록 설정하였다.
- 그럼 ViewModel을 살펴보자. View는 무조건 ViewModel 클래스에 선언한(profile_adjustment_view_model.dart) 메소드를 통해서만 model 데이터에 접근할 수 있다. 또한 계산 및 저장하는 함수도 가지고 있다.
class ProfileAdjustmentViewModel {
///View에서 Model을 접근할 수 있는 ViewModel 메소드.
final ProfileAdjustmentModel profileAdjustmentModel;
ProfileAdjustmentViewModel({required this.profileAdjustmentModel});
String get adjustmentTitle => profileAdjustmentModel.adjustmentTitle;
String get buttonTitle => profileAdjustmentModel.buttonTitle;
String get idInfoTitle => profileAdjustmentModel.idInfoTitle;
String get id => profileAdjustmentModel.id;
String get changePassword => profileAdjustmentModel.changePassword;
String get confirmChangePassword =>
profileAdjustmentModel.confirmChangePassword;
String get cancelButton => profileAdjustmentModel.cancelButton;
String get commonInfo => profileAdjustmentModel.commonInfo;
String get name => profileAdjustmentModel.name;
String get birth => profileAdjustmentModel.birth;
List<String> get birthUnit => profileAdjustmentModel.birthUnit;
String get phoneNumber => profileAdjustmentModel.phoneNumber;
String get confirmButton => profileAdjustmentModel.confirmButton;
Iconoir get clockIconPath => profileAdjustmentModel.clockIconPath;
String get phoneNumberHint => profileAdjustmentModel.phoneNumberHint;
String get addressInfo => profileAdjustmentModel.addressInfo;
String get homeAddress => profileAdjustmentModel.homeAddress;
String get homePhone => profileAdjustmentModel.homePhone;
///데이터 수정하는 기능을 담은 함수.
Map getPatchDataForm({
String? phone,
String? homePhone,
String? address,
String? detailAddress,
String? password,
String? rePassword,
ParentUser? parentUser,
}) {
Map data = {};
log('변경 전 비밀번호:$password');
log('변경 전 확인 비밀번호: $rePassword');
try {
if (phone!.isEmpty) {
data['phone'] = '';
} else {
data['phone'] = phone;
}
} catch (e) {
data['phone'] = '';
}
try {
if (homePhone!.isEmpty) {
data['home'] = '';
} else {
data['home'] = homePhone;
}
} catch (e) {
data['home'] = '';
}
try {
if (address!.isEmpty) {
data['address'] = '';
} else {
data['address'] = address;
}
} catch (e) {
data['address'] = '';
}
try {
if (detailAddress!.isEmpty) {
data['detail_address'] = '';
} else {
data['detail_address'] = detailAddress;
}
} catch (e) {
data['detail_address'] = '';
}
try {
if (password!.isEmpty) {
data['password'] = '';
} else {
data['password'] = password;
}
} catch (e) {
data['password'] = '';
}
try {
if (rePassword!.isEmpty) {
data['re_password'] = '';
} else {
data['re_password'] = rePassword;
}
} catch (e) {
data['re_password'] = '';
}
log('re_password: ${data['re_password']}');
log('password: ${data['password']}');
log('detail address : ${data['detail_address']}');
log('address: ${data['address']}');
log('home: ${data['home']}');
return data;
}
}
- 이 정보 수정 부분은 Stateful 위젯으로 만들어져 있기 때문에 initState에 해당 ViewModel을 선언한다.
profileAdjustmentViewModel = ProfileAdjustmentViewModel(
profileAdjustmentModel: ProfileAdjustmentModel());
- 아래와 같이 View 부분에 선언하여 데이터를 사용할 수 있다.
InputConfirmButton(
backgroundColor: greyFFF,
borderRadius: 10,
width: width,
height: height,
hasFocusNode: currentFocus!.hasFocus,
buttonBackgroundColor:complete ? baseColor : greyECE,
buttonColor: complete ? greyFFF : greyTextColor,
buttonFontSize: 16,
buttonFontWeight: nexonWeight,
buttonTitle: profileAdjustmentViewModel!.buttonTitle,);
API 부분 세팅 #
- 개인적으로 API 통신은 Restful APi를 사용하고 디렉터리를 따로 구성한다. 그 이유는 개인적으로 API 함수(Post, Get, Delete등)를 한곳에 모아놓으면 더 수정이 수월하기 때문이다.
- API를 통해 얻은 데이터는 with ChangeNotifier를 포함한 클래스에 저장하여 사용한다. 해당 예시는 프로필 개인정보 수정이기 때문에 'user_data_model.dart'에 데이터를 저장한다.

정리 #
- MVC 모델은 처음 사용하면 사용하는 코드양도 많이 늘어나고 번거로울 것이다. 하지만 협업하는 팀이라면 처음부터 분류화시키는 것이 훨씬 다음에 수정하기 쉽다는 것을 알 수 있다. 꼭 한 번 사용하고 MVC, MVVM 디자인 패턴 사용을 도와주는 많은 패키지가 있으니 참고 바란다.
참조링크 #