Flutter를 이용해서 회원가입을 만들고 있었다.
회원가입 Form의 UI 시나리오는 다음과 같다.
- 처음 접속하면 Email Input과 하단의 확인 버튼만 등장한다.
- 사용자가 Email을 입력하는 동시에 validation을 체크한다.
- validation에 통과하면, 확인 버튼이 enable 된다.
- 확인 버튼을 누르면 Password Input이 나타나면서 focus가 password input으로 이동한다.
- password input에서도 1-3번과 같이 동작한다.
- 확인 버튼을 누르면 핸드폰 인증 화면으로 넘어간다.
이런 시나리오로 구현하기 위해서 아래 방법으로 구현을 진행했다.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:toy_carrot_market/app/data/repository/signup_repository.dart';
import 'package:toy_carrot_market/style/colors.dart';
class SignupController extends GetxController {
final SignupRepository signupRepository;
SignupController({required this.signupRepository});
final GlobalKey<FormState> signupFormKey = GlobalKey<FormState>();
final emailController = TextEditingController();
final emailFocusNode = FocusNode();
final passwordController = TextEditingController();
final passwordFocusNode = FocusNode();
final RxInt _formIndex = 0.obs;
**final RxBool _isDisableButton = true.obs; // (3)**
int get formIndex => _formIndex.value;
bool get isDisableButton => _isDisableButton.value;
String? emailValidator(String? value) { // (2)
if (value == null) {
return null;
}
**_isDisableButton.value = true; // (3)**
if (value.isEmpty) {
return '이메일 주소를 입력해주세요.';
}
if (!value.isEmail) {
return '올바른 이메일 주소를 입력해주세요.';
}
**_isDisableButton.value = false;**
return null;
}
...생략
@override
void onClose() {
emailController.dispose();
passwordController.dispose();
passwordFocusNode.dispose();
super.onClose();
}
}
class SignupPage extends GetView<SignupController> {
SignupPage({Key? key}) : super(key: key);
final List<String> titleList = ['이메일 주소를 입력하세요.', '비밀번호를 입력하세요.'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Obx(
() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...생략,
Form(
key: controller.signupFormKey,
**autovalidateMode: AutovalidateMode.onUserInteraction, // (1)**
child: Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: SignupPages.pages.sublist(0, controller.formIndex + 1).map((item) => item).toList(),
),
ElevatedButton(
**onPressed: controller.isDisableButton ? null : controller.handleSignup, // (3)**
style: ...생략
),
child: const Text('확인'),
),
],
),
),
),
],
),
),
),
),
);
}
}
class SignupPages {
static final pages = [
const EmailInputWidget(),
];
}
class EmailInputWidget extends GetView<SignupController> {
const EmailInputWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextInputWidget(
labelText: '이메일 주소',
textEdintingController: controller.emailController,
focusNode: controller.emailFocusNode,
validator: controller.emailValidator,
);
}
}
class TextInputWidget extends StatelessWidget {
final String labelText;
final TextEditingController textEdintingController;
final String? Function(String?) validator;
final bool obscureText;
final FocusNode focusNode;
const TextInputWidget({
required this.labelText,
required this.textEdintingController,
required this.validator,
required this.focusNode,
this.obscureText = false,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(
top: 5.0,
bottom: 10.0,
),
child: TextFormField(
controller: textEdintingController,
**validator: validator, // (2)**
autofocus: true,
focusNode: focusNode,
obscureText: obscureText,
decoration: ...생략,
),
);
}
}
(1) 사용자가 입력하는 동시에 validation을 체크할 수 있도록 autovalidationMode를 onUserInteraction으로 적용했다.
(2) Email validation 함수를 만들고, Email 입력 값을 체크했다.
(3) Validation 체크에 따라서 확인 버튼이 enable/disable 될 수 있도록 isDisableButton 값을 RxBool로 지정하였다.
이렇게 구현을 하고, 실행을 했을 때, 아래의 에러가 발생한다.
setState() or markNeedsBuild() called during build.
이 에러가 발생하는 원인은 다음과 같다.
Flutter의 Lifecycle 중, build 단계에서 setState()를 실행하면 다음과 같은 에러가 발생한다.
위 코드에서 사용자가 입력하는 행위에 따라서 build가 다시 실행되는데, build가 완료되기 전에 버튼에 걸려있는 isDisableButton의 값을 변경하는 setState()가 실행되었기 때문이다.
에러의 원인은 쉽게 찾아볼 수 있었는데, 이를 해결하기 위해서 어떤 작업을 해야하는지 도저히 감이 잡히지 않았다.
이런저런 구글링 끝에 해결방안을 찾았고, 그 해결 방법은 Flutter의 Future.microtask를 이용하는 방법이다.
Future.microtask는 scheduleMicrotask를 사용해서 비동기적으로 동작하는 함수이고, 매개변수로 주어진 함수를 build가 완료된 후에 호출한다.
그렇기 때문에 위 코드에서 validation 부분을 아래와 같이 작성하게 되면, 사용자가 입력을 하고, build 실행이 완료된 후에 setState가 실행된다.
String? emailValidator(String? value) {
if (value == null) {
return null;
}
**Future.microtask(() => _isDisableButton.value = true);**
if (value.isEmpty) {
return '이메일 주소를 입력해주세요.';
}
if (!value.isEmail) {
return '올바른 이메일 주소를 입력해주세요.';
}
**Future.microtask(() => _isDisableButton.value = false);**
return null;
}
이렇게 구현하면 처음에 생각했던 시나리오대로 사용자의 입력과 동시에 validation을 체크할 수 있고, validation을 모두 통과했을 때, 버튼을 enable 할 수 있다.
P.S. 문제는 해결했는데, 아직까지도 저 방법이 가장 효율적인 방법인지는 모르겠다. 계속 찾아보고 찾아보고 찾아보자!!!
P.S. tistory 코드블럭에 dart가 없네... 아쉽..
'IT > Flutter' 카테고리의 다른 글
Apple Developer Program 등록하기 (0) | 2022.01.25 |
---|---|
[GetX] 같은 Controller를 사용하는 여러 페이지가 있는 경우 (1) | 2022.01.19 |
[Flutter] Incorrect use of ParentDataWidget. (0) | 2022.01.07 |
댓글