Flutter TextField 입력값 받기: controller·검증·dispose

핵심 요약
입력 순간 반응은 onChanged, 특정 시점에 값 조회는 controller가 편하며 폼 단위 검증은 TextFormField와 Form을 고려할 수 있습니다.

검수 범위
controller 생명주기, 공백 처리, 검증 책임과 화면 갱신을 TextField 공식 API 기준으로 검수했습니다.

Flutter TextField 입력값 받는 방법

Flutter에서 TextField 입력값을 받는 방법은 처음 배우는 분들이 가장 자주 마주치는 부분입니다. 눈으로는 입력이 되는데, 막상 그 값을 변수에 넣거나 버튼 눌렀을 때 가져오려고 하면 헷갈리기 쉽습니다. 이번 글에서는 가장 많이 쓰는 방식부터 실전 예제까지 하나씩 아주 쉽게 정리해드리겠습니다.

TextField 기본 이해

Flutter의 TextField는 사용자가 글자를 입력하는 입력창입니다.
문제는 입력창에 글자가 보이는 것과, 그 값을 코드에서 가져오는 것은 별개라는 점입니다.

예를 들어 사용자가 이름을 입력했다고 해도, 개발자가 따로 값을 받아두지 않으면 버튼을 눌렀을 때 그 내용을 제대로 사용할 수 없습니다.

보통은 아래 3가지 방식으로 많이 처리합니다.

  • 글자가 바뀔 때마다 받기
  • 버튼을 눌렀을 때 받기
  • 입력이 끝났을 때 받기

실무에서는 이 중에서도 onChangedTextEditingController를 가장 많이 사용합니다.

onChanged 사용 방법

입력할 때마다 값 받기

가장 쉬운 방법은 onChanged입니다.
이 방식은 사용자가 한 글자씩 입력할 때마다 바로 값을 받을 수 있습니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: TextFieldExample(),
    );
  }
}

class TextFieldExample extends StatefulWidget {
  const TextFieldExample({super.key});

  @override
  State createState() => _TextFieldExampleState();
}

class _TextFieldExampleState extends State {
  String inputText = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('TextField 예제')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              onChanged: (value) {
                setState(() {
                  inputText = value;
                });
              },
              decoration: const InputDecoration(
                labelText: '이름을 입력하세요',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            Text('입력한 값: $inputText'),
          ],
        ),
      ),
    );
  }
}

이 코드의 핵심은 아래 부분입니다.

onChanged: (value) {
  setState(() {
    inputText = value;
  });
}

사용자가 입력할 때마다 value 안에 현재 문자열이 들어옵니다.
그리고 그 값을 inputText 변수에 넣어 화면에 보여주는 방식입니다.

onChanged가 잘 맞는 경우

onChanged는 아래처럼 바로 반응해야 하는 상황에서 특히 편합니다.

  • 검색어 입력하면서 바로 목록 바꾸기
  • 글자 수 실시간 표시
  • 비밀번호 조건 확인
  • 입력값 미리보기

예를 들어 검색창을 만들 때는 사용자가 입력할 때마다 값을 받아야 하므로 onChanged가 잘 맞습니다.

Controller 사용 방법

버튼 누를 때 값 가져오기

실제로 가장 많이 쓰는 방법은 TextEditingController입니다.
이 방식은 입력창을 하나의 객체로 연결해두고, 필요할 때 값을 꺼내 쓰는 방식입니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: ControllerExample(),
    );
  }
}

class ControllerExample extends StatefulWidget {
  const ControllerExample({super.key});

  @override
  State createState() => _ControllerExampleState();
}

class _ControllerExampleState extends State {
  final TextEditingController nameController = TextEditingController();
  String result = '';

  @override
  void dispose() {
    nameController.dispose();
    super.dispose();
  }

  void getInputValue() {
    setState(() {
      result = nameController.text;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Controller 예제')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(
                labelText: '이름을 입력하세요',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: getInputValue,
              child: const Text('값 확인'),
            ),
            const SizedBox(height: 20),
            Text('입력한 값: $result'),
          ],
        ),
      ),
    );
  }
}

여기서 중요한 부분은 nameController.text입니다.

result = nameController.text;

이 한 줄로 현재 입력창에 들어 있는 값을 가져올 수 있습니다.

dispose를 꼭 써야 하는 이유

TextEditingController를 만들었으면 화면이 사라질 때 정리도 해줘야 합니다.

@override
void dispose() {
  nameController.dispose();
  super.dispose();
}

이 부분을 빼먹으면 메모리 낭비가 생길 수 있습니다.
처음에는 잘 돌아가는 것처럼 보여도, 습관처럼 꼭 넣는 것이 좋습니다.

onSubmitted 사용 방법

입력 완료 시점에 값 받기

사용자가 키보드에서 완료 버튼을 눌렀을 때 값을 받고 싶다면 onSubmitted를 사용할 수 있습니다.

TextField(
  onSubmitted: (value) {
    print('입력 완료 값: $value');
  },
)

이 방식은 아래처럼 쓰기 좋습니다.

  • 검색창에서 엔터 누를 때 검색 실행
  • 입력 마친 뒤 바로 다음 동작 처리
  • 채팅 입력 후 전송

onChanged는 입력 중간마다 실행되고,
onSubmitted는 입력이 끝났을 때 실행된다는 차이가 있습니다.

가장 많이 쓰는 실전 예제

TextField와 버튼 함께 쓰기

실제로는 입력창에 값을 적고 버튼을 눌러서 확인하는 경우가 많습니다.
아래 예제 하나만 이해해도 기본은 충분히 잡을 수 있습니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: SimpleInputPage(),
  ));
}

class SimpleInputPage extends StatefulWidget {
  const SimpleInputPage({super.key});

  @override
  State createState() => _SimpleInputPageState();
}

class _SimpleInputPageState extends State {
  final TextEditingController emailController = TextEditingController();
  String message = '아직 입력값이 없습니다.';

  @override
  void dispose() {
    emailController.dispose();
    super.dispose();
  }

  void checkValue() {
    final text = emailController.text.trim();

    setState(() {
      if (text.isEmpty) {
        message = '이메일을 입력해주세요.';
      } else {
        message = '입력한 이메일: $text';
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('입력값 받기'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: '이메일 입력',
                hintText: 'example@email.com',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: checkValue,
                child: const Text('확인하기'),
              ),
            ),
            const SizedBox(height: 16),
            Text(message),
          ],
        ),
      ),
    );
  }
}

이 예제에서 꼭 봐야 할 부분

첫 번째는 공백 제거입니다.

final text = emailController.text.trim();

사용자가 앞뒤에 띄어쓰기를 넣는 경우가 있기 때문에 trim()을 자주 함께 씁니다.

두 번째는 빈 값 검사입니다.

if (text.isEmpty) {
  message = '이메일을 입력해주세요.';
}

값을 받았다고 끝이 아니라, 비어 있는지 확인하는 과정도 꼭 필요합니다.

여러 개 입력창 받을 때 방법

변수 따로 만들기

입력창이 여러 개라면 컨트롤러도 각각 따로 만들어야 합니다.

final TextEditingController idController = TextEditingController();
final TextEditingController pwController = TextEditingController();

그리고 버튼을 누를 때 각각 값을 꺼내 쓰면 됩니다.

final id = idController.text;
final pw = pwController.text;

예제는 아래처럼 만들 수 있습니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: LoginPage(),
  ));
}

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State createState() => _LoginPageState();
}

class _LoginPageState extends State {
  final TextEditingController idController = TextEditingController();
  final TextEditingController pwController = TextEditingController();
  String result = '';

  @override
  void dispose() {
    idController.dispose();
    pwController.dispose();
    super.dispose();
  }

  void login() {
    final id = idController.text.trim();
    final pw = pwController.text.trim();

    setState(() {
      result = '아이디: $id / 비밀번호: $pw';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('로그인 예제')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: idController,
              decoration: const InputDecoration(
                labelText: '아이디',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: pwController,
              obscureText: true,
              decoration: const InputDecoration(
                labelText: '비밀번호',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: login,
              child: const Text('로그인'),
            ),
            const SizedBox(height: 16),
            Text(result),
          ],
        ),
      ),
    );
  }
}

입력 검증 같이 처리하기

빈칸 검사부터 해보기

입력값을 받는 것만큼 중요한 것이 검증입니다.
아무 값이나 그대로 넘기면 회원가입, 로그인, 검색 기능에서 문제가 생길 수 있습니다.

아주 간단한 예시는 아래와 같습니다.

void checkInput() {
  final text = nameController.text.trim();

  if (text.isEmpty) {
    print('값을 입력해주세요.');
    return;
  }

  print('입력 완료: $text');
}

글자 수 검사 넣기

예를 들어 닉네임은 2글자 이상만 받도록 하고 싶다면 이렇게 쓸 수 있습니다.

void checkNickname() {
  final nickname = nameController.text.trim();

  if (nickname.isEmpty) {
    print('닉네임을 입력해주세요.');
    return;
  }

  if (nickname.length < 2) {
    print('닉네임은 2글자 이상 입력해주세요.');
    return;
  }

  print('사용 가능한 입력값입니다.');
}

입력값을 받는 것과 검사하는 것은 거의 항상 같이 다닌다고 생각하면 됩니다.

자주 하는 실수 정리

controller 연결 안 한 경우

컨트롤러를 만들었는데 TextField에 연결하지 않으면 값이 안 잡힙니다.

TextField(
  controller: nameController,
)

이 연결을 빼먹지 않아야 합니다.

dispose 안 한 경우

TextEditingController를 사용했으면 dispose()도 꼭 써야 합니다.
작은 예제에서는 티가 안 나도 실제 앱에서는 중요합니다.

setState 빠뜨린 경우

화면에 결과를 보여주는 코드인데 setState()를 안 쓰면 값은 바뀌어도 화면이 그대로일 수 있습니다.

setState(() {
  result = nameController.text;
});

trim 안 써서 공백까지 저장한 경우

사용자가 실수로 띄어쓰기를 넣을 수 있으니, 저장하거나 검사하기 전에 trim()을 붙이는 습관이 좋습니다.

어떤 방법을 써야 하는지 정리

상황별 선택 방법

입력 중간마다 바로 받고 싶다
onChanged

버튼 눌렀을 때 가져오고 싶다
TextEditingController

입력이 끝났을 때 한 번만 받고 싶다
onSubmitted

처음에는 TextEditingController부터 익히는 것이 가장 좋습니다.
버튼 클릭, 로그인, 검색, 회원가입처럼 자주 만드는 기능에 바로 쓸 수 있기 때문입니다.

결론

Flutter에서 TextField 입력값을 받는 방법은 생각보다 어렵지 않습니다. 가장 많이 쓰는 방식은 onChanged, TextEditingController, onSubmitted 세 가지이며, 목적에 따라 골라 쓰면 됩니다. 입력할 때마다 바로 값을 확인하고 싶다면 onChanged가 편하고, 버튼을 눌렀을 때 현재 입력값을 가져오고 싶다면 TextEditingController가 가장 실용적입니다. 입력 완료 시점에 한 번만 처리하고 싶을 때는 onSubmitted가 잘 맞습니다.

처음 배우는 단계라면 TextEditingController부터 익히는 편이 좋습니다. 로그인, 회원가입, 검색창, 문의 폼처럼 실제 앱에서 자주 쓰이기 때문입니다. 여기에 trim()으로 공백을 정리하고, 빈칸 검사와 dispose()까지 함께 익혀두면 실무에서도 바로 활용하기 좋습니다.

결국 중요한 것은 입력창에 글자가 보이는 것만으로 끝나는 것이 아니라, 그 값을 원하는 시점에 정확하게 가져와서 검사하고 활용하는 것입니다. 이 부분만 제대로 이해하면 TextField는 훨씬 쉽게 다룰 수 있습니다.


검증 기준과 참고 자료

이 글은 2026-09-08에 아래 공식 문서와 공개 기술 문서를 기준으로 내용과 용어를 다시 검수했습니다. 제품 버전과 기기 제조사에 따라 화면 이름은 달라질 수 있으므로 실제 화면과 공식 문서를 함께 확인하세요.

관련 글