Skip to content

flutter mvc #
Find similar titles

Structured data

Category
Programming

플러터(Flutter) #

플러터란 #

MVC 디자인 패턴 #

  1. 디자인 패턴을 사용해야하는 이유
  2. Model, View, Controller 역할
  3. 실제 예시
  4. API 부분
  5. 정리

MVC 디자인 패턴을 왜 사용해야 할까? #

MVC 패턴, 여기서 파생된 MVVM 패턴은 인터페이스, 데이터, 데이터 제어에 사용되는 소프트웨어 디자인 패턴이다. Model, View Model, View는 각자의 역할을 하며(관심사 분리) 서로 역할을 분리하며 팀 단위 프로젝트에 큰 도움이 된다.
한 페이지 안에 변수, 함수, 위젯을 한꺼번에 선언하면 어떻게 될까? 아마 그 UI에 해당하는 dart 파일에 들어가서 처음부터 끝까지 읽으며 변수를 변경하거나 함수를 수정할 것이다. 만약 해당 뷰가 내가 작성한 코드가 아니라면? 아마 수정할 때마다 코드를 읽어야 해서 많은 시간을 잡아먹을 것이다.
하지만 Model에 앱에 필요한 내용을 저장하면 해당 부분만 가서 변경할 수 있다. 코드를 전부 읽지 않아도 특정 위치에 변경 지점이 있다는 것을 인지하고 그 부분에 접근하면 시간 단축을 할 수 있을 것이다. 이제 각 역할이 무엇인지 알아보자.

Model, View, Controller 역할 #

Model은 데이터를 저장하는 클래스이다. 대부분 앱은 프로필에 내 정보 수정 부분이 있다. 여기서 Model에 해당하는 부분은 제목, 부제목, 라벨에 해당하는 부분이다. (ex: 계정 정보, 아이디, 변경할 비밀번호)
Controller 즉 ViewModel은 앱에서 사용자 입력(비밀번호 입력 등)에 대한 응답으로 Model의 데이터를 업데이트한다.
View는 말 그대로 앱의 데이터를 보여주는 UI이다.

Image

실제 예시 #

위 사진에 해당하는 부분을 나눈 코드의 일부이다. 먼저 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'에 데이터를 저장한다.

Image

정리 #

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

참조링크 #

0.0.1_20210630_7_v33