| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- SpringBoot
- Java
- jvm
- webflux
- RDBMS
- monitoring
- Kubernetes
- 동시성제어
- prometheus
- 성능최적화
- 트랜잭션
- helm
- Kotlin
- DevOps
- 백엔드
- 데이터베이스
- redis
- grafana
- NIO
- 백엔드개발
- CloudNative
- mysql
- JPA
- netty
- docker
- spring boot
- 성능 최적화
- kafka
- GitOps
- selector
- Today
- Total
유성
Flutter에서 TextField를 쓰지 않는 이유와 iOS의 KeyboardActions 본문
입력을 받는 TextField
단순하게 사용자의 입력을 받기 위해서는 TextField를 사용하면 된다.
Flutter로 입력 폼을 만들다 보면 이런 순간이 온다.
단순한데 그냥 TextField를 쓰면 되는거 아닌가?
굳이 생명주기 관리가 필요한 Controller 까지 써야 하나?
결론부터 말하면 실서비스 기준에서는 Controller 없이 버티기 어렵다.
이 글은 그 이유를 기능 단위로 정확히 끊어서 정리한 기록이다.
TextEditingController는 "값 저장용" 이 아니다.
TextEditingController는 입력값을 가져오는 용도로 볼 수 있는데,
하지만 실제 역할은 입력값 그 자체의 소유자다.
| 기능 | Controller 없을 때 | Controller 있을 때 |
| 외부에서 값 변경 | 불가능 | 가능 |
| 다른 위젯에서 같은 값 사용 | 불가능 | 가능 |
| 자동완성 / 복사 붙여넣기 | 제한적 | 완전 제어 |
| 입력값 실시간 추적 | 불안정 | 안정 |
즉, Controller는 "입력 상태의 단일 진실 원천(Single Source of Truth)"이다.
Controller 없으면 반드시 발생하는 문제들
실제 구현하면서 가장 많이 발생하는 상황은 이것이다.
1. 외부 버튼으로 입력값 바꿀 때
_nameController.text = '러닝';
Controller가 없다면 이렇게 지정된 값을 채우는 것이 불가능하다.
2. 자동완성 목록 터치 시 입력 반영
onTap: () {
_nameController.text = suggestion;
}
이 또한 Controller를 사용해야 하며, TextField는 이 변경 사실을 알수 없다.
3. 여러 필드가 하나의 값에 의존할 때
A값과 B의 값을 각각 받아서 A+B를 만들 경우 Controller가 없으면
값 변경 감지를 setState로만 억지로 추적해야 한다.
FormField와 연결하려면 Controller는 필수다
FormField는 검증과 저장 흐름만 책임진다.
하지만 실제 화면의 입력 상태는 TextField + Controller가 가진다.
그래서 반드시 이 구조가 필요하다.
[Controller] ←→ [TextField]
↑ ↓
외부 변경 사용자 입력
↑ ↓
[FormField]
↓
validate / save
이 구조를 만들지 않으면 아래와 같은 증상이 발생한다.
- validate 통과했는데 화면 값이 다름
- controller 값 바꿨는데 validation이 안 됨
- 저장 시 이전 값이 들어감
사용 방법
그래서 코드는 다음과 같이 작성한다.
// 자원 획득
final TextEditingController _ageController = TextEditingController();
@override
void initState() {
super.initState();
_ageController.addListener(_controllerListener); // 변경 감지
}
@override
void dispose() {
_ageController.removeListener(_controllerListener); // 변경 감지 제거
_ageController.dispose(); // 메모리 해제
super.dispose();
}
void _controllerListener() {
if (!mounted) return;
setState(() {}); // UI 갱신
}
-----
...
TextField(
controller: _ageController, // TextField와 controller 연결
...
),
자원의 생성부터 해제까지의 생명주기를 관리한다는 관점에서 복잡성이 상대적으로 크고 만약 dispose가 정상적으로 이루어지지 않은 경우 메모리 누수가 발생할 수 있다.
여기서는 Controller -> FormField 동기화, FormField -> Controller 재반영과 같이 양방향으로 연결된다.
꽤나 복잡한 방식은 맞지만 유지보수 측면에서는 Controller, FormField 방식이 더 효율적이라고 생각한다.
번외, iOS 에서는 왜 KeyboardActions가 추가로 필요할까?
Android 숫자 키패드에는 기본적으로 완료 버튼이 있다.
하지만 iOS는 숫자 키패드에 "완료" 버튼이 존재하지 않는다.
여기서 발생하는 문제는 다음과 같다.
- 포커스가 해제되지 않는다.
- 키보드가 항상 화면을 가린다.
- 그로인해 bottomNav를 쓰는경우 다음 단계로 넘어가지 못한다.
이 경우 복잡하지만 FocusNode로 처리가 필요하다.
keyboard_actions: ^4.2.0
iOS의 경우 위 라이브러리를 설치하고, 위젯의 상단에 config를 붙여주어, 해당 node가 포커스 됐을 때 버튼을 생성하게끔 한다.
final _ageFocusNode = FocusNode();
@override
void dispose() {
_ageFocusNode.dispose();
}
KeyboardActions(
config: KeyboardActionsConfig(
actions: [
KeyboardActionsItem(
focusNode: _ageFocusNode,
toolbarButtons: [
(node) => TextButton(
onPressed: () => node.unfocus(),
child: const Text('완료'),
),
],
),
],
),
child: Form(...),
)
적용 후 생성된 "완료" 버튼 작동은 node.unfocus()가 실행된다.

마치며,
TextEditingController는 단순히 값을 전달하는 역할을 넘어서,
값을 소유하고 변화 감지, 검증, 포커스 제어, 외부 동기화까지 가능하게 하는 핵심 객체라고 보는 게 가장 정확하다.
그래서 dispose, listener 같은 자원 관리가 필수가 되는 것이고, 이 구조는 Flutter가 UI를 단순 위젯이 아닌 상태 중심 아키텍처로 다루기 때문이기도 하다.
반면 iOS의 숫자 키패드에 기본 '완료' 버튼이 없는 UX는 아직도 적응되지 않는다.
사용자 편의보다 "일관된 디자인 철학"을 더 우선한 선택처럼 느껴지고,
결국 개발자는 KeyboardActions 같은 도구로 이를 보완해야 한다.
기술적으로는 이해되지만,
여전히 납득되지는 않는 UI 결정이다.
'기타' 카테고리의 다른 글
| DB Index 로 불가능해 역색으로 해결한 성능 개선 (0) | 2026.01.07 |
|---|---|
| 좋은 개발자란 무엇인가(좋은 문제정의는 어떻게 하는가)? (4) | 2025.08.06 |
| SSL/TLS 통신은 어떻게 만들어졌는지? 대칭키/비대칭키/해시/인증서 (0) | 2025.07.28 |
| 이 코드 누가 짰어? 신입과 팀장의 시선으로 풀어보는 레거시 코드 (5) | 2025.07.24 |
| Fleet 단축키 모음 (0) | 2025.04.12 |