Form 위젯
- TextField는 단순히 하나의 텍스트 입력을 다루는데 반해서, Form은 그 자체로 입력 필드를 가지고 있지 않지만, FormField 위젯들을 그룹화하여 관리하며, 복잡한 유효성 검사를 보다 쉽게 할 수 있는 위젯
- 다른 위젯과 달리 Form 위젯은 자체적인 화면을 제공하지는 않으며, 사용자가 입력한 데이터의 유효성 검증, 데이터 관리 관련 기능을 제공한다.
- Form 위젯 내에서 TextFormField 위젯을 사용하여 각 데이터 입력을 받는 것이 일반적이다.
- Form 위젯 작성 방법은 다음과 같다.
1. Form 위젯을 위한 GlobalKey를 만들어야 한다. GlobalKey는 FormState 전체에 엑세스하는데 사용되며, 이 객체는 폼 데이터의 유효성을 검사하고 저장하는데 사용된다.
final _formKey = GlobalKey<FormState>();
2. TextFormField 위젯들의 폼 컨트롤을 위젯을 Form 위젯으로 래핑하고, _formKey를 Form 위젯의 key 속성으로 설정
Form(
key: _formKey,
child: Column(
children: [
// 폼 컨트롤들
],
),
);
3. TextFormField 위젯 또는 다른 폼 컨트롤을 Form 위젯에 추가한다. 각 폼 컨트롤은 validator 함수와 선택적인 onSaved 함수를 가져야 함
- validator 함수는 사용자가 폼을 제출할 때 호출되며, 입력이 유효한지 확인한다.
- 입력이 유효하지 않으면 validator는 오류 메시지를 포함하는 문자열을 반환해야 한다.
- 입력이 유효하다면 validator는 null을 반환해야 한다.
- onSaved 함수는 폼이 저장될 때 호출되며, 사용자가 입력한 값을 변수나 데이터 모델에 저장해야 한다.
TextFormField(
decoration: InputDecoration(
labelText: 'Email',
),
validator: (value) {
if (value == null || value.isEmpty) {
return '이메일을 입력하세요';
}
return null;
},
onSaved: (value) {
_email = value;
},
),
4. 폼에 제출 버튼을 추가하고 폼 제출을 처리할 함수를 정의
- _submitForm 함수는 FormState 객체의 validate 메서드(_formKey.currentState!.validate())를 사용하여 폼 유효성을 검사하고
- FormState 객체의 save 메서드 (_formKey.currentState!.save())를 사용하여 폼 데이터를 저장해야 한다.
- _formKey.currentState!에서 _formKey는 GlobalKey 객체이고, 여기에서 currentState는 FormState 객체이다.
- 해당 객체가 없지 않다는 것을 명시적으로 알려주기 위해 !(Exclamation mark)를 기재한 것이다.
- 폼 데이터가 유효하면 _submitForm 함수는 데이터를 의도한 대로 처리하게 된다.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp8());
}
class MyApp8 extends StatefulWidget {
const MyApp8({super.key});
@override
State<MyApp8> createState() => _MyApp8State();
} // end of class
class _MyApp8State extends State<MyApp8> {
// Form 위젯 만들어 보기
final _formKey = GlobalKey<FormState>();
String _name = '';
String _email = '';
String _password = '';
String _errorMessage = '';
// 멤버 함수 만들어보기
void _submitForm() {
// 유효성 검사
// UI 상태 변경
// ---> 통신 요청
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Form Example'),
),
body: Container(
padding: const EdgeInsets.all(16.0),
// form 위젯은 위젯들을 구분할 수 있는 키가 필요하다.
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'name',
errorStyle: TextStyle(color: Colors.blue, fontSize: 10),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '이름을 입력하세요';
}
return null;
},
// value 매개변수 값이 null 이 될 수 있다.
onSaved: (value) {
// value! 강제 null 아니라고 명시 함
_name = value!;
},
),
TextFormField(
decoration: InputDecoration(
labelText: 'email',
errorStyle: TextStyle(color: Colors.blue, fontSize: 10),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '이메일을 입력하세요';
}
return null;
},
// value 매개변수 값이 null 이 될 수 있다.
onSaved: (value) {
// value! 강제 null 아니라고 명시 함
_email = value!;
},
),
TextFormField(
decoration: InputDecoration(
labelText: 'password',
errorStyle: TextStyle(color: Colors.blue, fontSize: 10),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '비밀번호을 입력하세요';
}
return null;
},
// value 매개변수 값이 null 이 될 수 있다.
onSaved: (value) {
// value! 강제 null 아니라고 명시 함
_password = value!;
},
),
const SizedBox(height: 20),
ElevatedButton(onPressed: () {}, child: Text('Submit')),
const SizedBox(height: 20),
Text(
_errorMessage,
style: TextStyle(color: Colors.red),
),
],
),
),
),
),
);
}
} // end of class
Form 위젯 연습

import 'package:flutter/material.dart';
void main() {
runApp(FeedbackFormApp());
}
// 한 파일 안에 여러 개의 클래스를 만들 수 있다.
class FeedbackFormApp extends StatelessWidget {
const FeedbackFormApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.redAccent),
),
home: FeedbackForm(),
);
}
}
// 상태 변경 가능한 UI 선언
class FeedbackForm extends StatefulWidget {
const FeedbackForm({super.key});
@override
State<FeedbackForm> createState() => _FeedbackFormState();
}
class _FeedbackFormState extends State<FeedbackForm> {
final _formKey = GlobalKey<FormState>(); // 폼 상태를 추적하기 위한 글로벌 키
int _rating = 0;
bool _subscribe = false;
String _name = '';
String _email = '';
String _comment = '';
String _successMessage = '';
double _satisfaction = 0.0;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('FeedbackForm'),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
),
body: SingleChildScrollView(
// formField 터치 시 소프트 키보드가 올라 온다. 여백 공간을 적절히 주어야 한다.
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: '이름',
hintText: '이자두',
border: OutlineInputBorder()),
validator: (value) {
if (value == null || value.isEmpty) {
return '이름을 입력해주세요.';
} else {
// 정상적으로 입력했다면,
return null;
}
}, // end of validator
onSaved: (value) {
_name = value!;
},
),
const SizedBox(height: 16.0),
TextFormField(
decoration: InputDecoration(
labelText: '이메일',
hintText: 'pulm@naver.com',
border: OutlineInputBorder()),
validator: (value) {
if (value == null || value.isEmpty) {
return '이메일을 입력해주세요.';
} else {
// 정상적으로 입력했다면,
return null;
}
}, // end of validator
onSaved: (value) {
_email = value!;
},
),
const SizedBox(height: 16.0),
TextFormField(
maxLines: 4, // 여러 줄 입력 가능 (텍스트 칸 넓히기)
decoration: InputDecoration(
labelText: 'comment',
hintText: '경험을 공유해주세요.',
border: OutlineInputBorder()),
validator: (value) {
if (value == null || value.isEmpty) {
return '이름을 입력해주세요.';
} else {
// 정상적으로 입력했다면,
return null;
}
}, // end of validator
onSaved: (value) {
_comment = value!;
},
),
const SizedBox(height: 24),
Text(
'저희 앱을 어떻게 평가하시겠습니까?',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (index) {
return IconButton(
onPressed: () {
setState(() {
// 추후 버그 수정
_rating = index + 1; // 선택한 점수로 설정
});
},
color: Colors.pink,
icon: Icon(
_rating > index ? Icons.star : Icons.star_border),
);
}),
),
const SizedBox(height: 24),
Text(
'고객 서비스 만족도는 어느 정도입니까?',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary),
),
Slider(
value: _satisfaction,
// 추후 변수로 치환
min: 0,
max: 10,
onChanged: (value) {
setState(() {
print('value $value');
_satisfaction = value!;
});
},
divisions: 10,
// 슬라이더 구간 설정
label: _satisfaction.toString(),
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('뉴스레터를 구독하시겠습니까?'),
value: _subscribe,
onChanged: (value) {
setState(() {
_subscribe = value;
});
},
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// 통과
_formKey.currentState!.save(); // 실행
// 변수에 값 할당
setState(() {
_successMessage = '성공적으로 제출되었습니다.';
});
} else {
// 실패
setState(() {
_successMessage = '';
});
}
},
child: const Text('제출'),
),
// dart 문법 활용
if (_successMessage.isNotEmpty)
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Text(
_successMessage,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 16
),
),
)
],
),
),
),
),
);
}
}
GlobalKey란
GlobalKey는 Flutter에서 위젯 트리 내에서 특정 위젯을 유일하게 식별할 수 있는 키이다. 이 키를 사용하면 해당 위젯이나 그 상태(State)에 직접 접근하여 조작할 수 있다.
상태(State)란?
위젯이 현재 가지고 있는 데이터나 속성을 의미하며, 시간이 지남에 따라 변경할 수 있는 값이다.
StatelessWidgt과 StatefulWidget의 차이
StatelessWidget
- 정의 : 상태를 가지지 않는 위젯. 내부에 데이터나 속성을 가질 수 있으나 내부에 변경 가능한 데이터나 속성이 없기 때문에 자체적으로 UI가 업데이트되지 않는다.
- 하지만(추가 설명)
- 외부로부터의 변화 : StatelessWidget은 부모 위젯이나 외부에서 전달된 데이터가 변경되면 재빌드되어 UI가 업데이트될 수 있다.
- 한정된 업데이트 : 하지만 자체적으로 상태를 관리하거나 변경하지 않으므로, 내부적인 상태 변화로 인한 UI 업데이트는 발생하지 않는다.
StatefulWidget
- 정의 : 상태(State)를 가지는 위젯. 시간이 지남에 따라 상태가 변경될 수 있고, 이에 따라 UI도 업데이트된다.
- 추가 설명 :
- 상태관리 : StatefulWidget은 State 객체를 통해 상태를 관리하며, 상태가 변경될 때 마다 setState() 메서드를 호출하여 UI를 갱신한다.
- 독립성 : 자체적으로 상태를 관리하기 때문에, 외부의 변화뿐만 아니라 내부적인 상태 변화에도 대응할 수 있다.
GlobalKey의 주요 기능
- 위젯 식별 : 동일한 타입의 위젯이 여러 개 있더라도, GlobalKey를 사용하면 특정 위젯을 구분할 수 있다.
- 상태 접근 : StatefulWidget의 상태(State)에 직접 접근하여 값을 읽거나 변경할 수 있다.
- 위젯 위치 정보 얻기 : 위젯의 위치나 크기 등의 정보를 얻어올 수 있다.
'Flutter' 카테고리의 다른 글
플러터 기본 2 (3) | 2024.11.07 |
---|---|
플러터 기본기 - 위젯 사용법 (0) | 2024.11.06 |
플러터 기본기 다지기 (2) | 2024.11.05 |
dart(함수, 메서드) (0) | 2024.09.06 |
dart Null Safety (1) | 2024.09.06 |