본문 바로가기
Programming/Flutter

[모로로그] 문의 화면 만들기 - #1 화면 구성

by NAMP 2023. 7. 17.

사용자가 의견을 전달하기 위한 화면을 만든다. (진행중이라서 내용이 변경될 수 있음.)

전달하는 정보는,

  • 이름,
  • 이메일,
  • 내용

으로 구성한다. (추가적으로 플랫폼 종류, 버전정보를 포함한다.)

대략적인 화면 구성은 아래와 같다.

문의 화면

라우터 추가

라우터 내용은 lib/route/routes.dart 파일에서 추가한다.

./lib/route/
└── routes.dart

/contact 이름으로 GetPage 를 추가 한다.

class Routes {
  ...
  static const contact = '/contact';

  static const defaultTransition = Transition.downToUp;

  static final pages = <GetPage>[
    ...
    GetPage(
      name: contact,
      page: () => const ContactPage(),
      binding: ContactBinding(),
      transition: defaultTransition,
    ),
    ...
  ];
}

전체내용은 이곳에서 확인 ⇨ routes

화면 파일 생성

lib/view/contact/page/contact_page.dart 파일을 만든다. (binding, controller, page 파일을 같이 만든다.)

./lib/view/contact
├── organism
└── page
    ├── contact_binding.dart
    ├── contact_controller.dart
    └── contact_page.dart

contact_controller.dart

class ContactController extends GetxController {
  final formKey = GlobalKey<FormState>();

  var nameController = TextEditingController();
  var emailController = TextEditingController();
  var contentController = TextEditingController();

  Future send() async {}
}

데이터 전송을 위한 Form Key 생성한다.
이름, 이메일, 내용을 입력하기 위한 컨트롤러를 생성한다.
전송 부분은 이후에 완성하기로 한다.

contact_binding.dart

class ContactBinding implements Bindings {
  @override
  void dependencies() {
    Get.put(ContactController());
  }
}

contact_page.dart

class ContactPage extends GetView<ContactController> {
  const ContactPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const BasicAppbar('문의하기'),
      body: SingleChildScrollView(
        child: SmallPadding(
          child: buildForm(),
        ),
      ),
    );
  }
}  

앱바에 공통 스타일을 적용하기 위해서 BasicAppbar 를 사용한다.
공통 Padding 적용을 위해서 SmallPadding 을 사용한다.

화면 구성

이름, 이메일, 내용, 저장 버튼을 Column 으로 구성한다.

buildForm() {
  return Form(
    key: controller.formKey,
    child: Column(
      children: [      
        buildName(),
        buildEmail(),
        buildContent(),        
        MainButton(),
      ],
    ),
  );
}

이름 필드

buildName() {
  return TextFormField(
    decoration: InputDecoration(labelText: t.label.name),
    controller: controller.nameController,
    textInputAction: TextInputAction.done,
    onSaved: (String? newValue) => newValue?.trim(),
    validator: (String? value) => ValidatorUtil.minLength(value, length: 1),
  );
}

라벨은 다국어 지원을 위해서 slang 을 사용하였다.

ValidatorUtil

유효성 검증 함수들을 한곳으로 모은다.

class ValidatorUtil {
  static String? minLength(String? value, {int length = 1}) {
    if (value == null || value.isEmpty || value.trim().length < length) {
      if (length == 1) return t.validator.more_than_one_length;
      return t.validator.more_than_length(length: length);
    }
    return null;
  }

  static String? email(String? value) {
    final emailReg = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
    if (value == null || emailReg.hasMatch(value) == false) {
      return t.validator.email_format;
    }
    return null;
  }
}

이메일 필드

buildEmail() {
  return TextFormField(
    decoration: InputDecoration(labelText: t.label.email),
    controller: controller.emailController,
    textInputAction: TextInputAction.done,
    onSaved: (String? newValue) => newValue?.trim(),
    validator: (String? value) => ValidatorUtil.email(value),
  );
}

내용 필드

buildContent() {
  return TextFormField(
    decoration: InputDecoration(labelText: t.label.content),
    controller: controller.contentController,
    textInputAction: TextInputAction.done,
    onSaved: (String? newValue) => newValue?.trim(),
    validator: (String? value) => ValidatorUtil.minLength(value, length: 10),
    minLines: 5,
    maxLines: 10,
  );
}

저장 버튼

폼을 검사하고 저장을 진행한다.
문의하는 내용이니 서버로 전송하도록 하자.

MainButton(
    onPressed: () async {
      if (controller.formKey.currentState!.validate()) {
        controller.formKey.currentState!.save();
        var res = await controller.send();
        if (res != null) {
          Get.snackbar(
            t.title.contact.send,
            t.message.contact.after_send,
          );
          Get.back();
        }
      }
    },
    text: t.button.send),

화면

여기까지 진행한 화면이다.

추가 정보

문의한 사용자의 환경에 대한 정보를 얻기 위해서, 플랫폼 정보, 패키지 정보를 추가한다.

사용한 라이브러리 목록

pubspec.yaml 에 추가

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations: 
    sdk: flutter        

  ...
  package_info_plus: ^4.0.2
  platform_info: ^4.0.2

추가 정보는 SettingService 에서 관리한다.

class SettingService extends GetxService {
  static SettingService get to => Get.find();
  late PackageInfo packageInfo;
  late String appName, packageName, version, buildNumber;
  late Platform platform;

  init() async {
    packageInfo = await PackageInfo.fromPlatform();

    appName = packageInfo.appName;
    packageName = packageInfo.packageName;
    version = packageInfo.version;
    buildNumber = packageInfo.buildNumber;
    platform = Platform.instance;
  }
}

서비스 파일의 위치는 다음과 같다.

./lib/service/
├── ...
├── setting_service.dart
└── ...

서비스 파일이니까, system 에서 추가한다.

class System {
  static Future init() async {
    WidgetsFlutterBinding.ensureInitialized();
    initializeJsonMapper(); // dart_json mapper 초기화
    putServices();
    await initServices();
  }

  static putServices() {
    ...
    Get.put(SettingService());
    ...
  }

  static initServices() async {
    ...
    await SettingService.to.init();
    ...
  }
}

링크

'Programming > Flutter' 카테고리의 다른 글

일상기록을 위한 서비스를 만든다  (0) 2023.07.10
[flutter] layoutbuilder  (0) 2023.03.06
mobx - 기본구성  (0) 2020.09.19
[Flutter] json_serializable 사용하기  (0) 2019.06.13

댓글