티스토리 뷰

Flutter에서 텍스트 필드의 변화에 따라 특정 동작을 실행하는 것은 매우 유용한 기능입니다. 특히, 검색 화면에서 입력 중 자동 완성 기능을 구현하거나, 입력 값에 따라 실시간으로 결과를 업데이트할 때 필수적인 요소입니다. 이 글에서는 onChanged 콜백과 TextEditingController를 통해 Flutter에서 텍스트 필드의 변화를 감지하는 방법을 알아봅니다.

참고. Handle changes to a text field

Flutter 텍스트 필드 변경 사항을 감지하는 두 가지 방법

1. onChanged 콜백 사용하기

onChanged 콜백은 가장 간단한 방법으로, 사용자가 텍스트 필드에 입력을 할 때마다 즉시 호출됩니다. 이 콜백은 TextFieldTextFormField 위젯에 직접 추가할 수 있습니다.

TextField(
  onChanged: (text) {
    print('현재 텍스트: $text (길이: ${text.characters.length})');
  },
),

위 코드처럼 onChanged 콜백에 함수를 전달하면, 사용자가 입력할 때마다 해당 함수가 호출됩니다. 예를 들어, 입력된 텍스트를 실시간으로 확인할 수 있어 자동 완성 기능이나 필터링 기능을 구현할 때 유용합니다.

onChanged 콜백의 장점

  • 간단하고 빠름: 구현이 매우 간단하고 텍스트 입력과 동시에 즉각적으로 콜백이 실행됩니다.
  • 초기 설정 불필요: 추가적인 컨트롤러나 리스너 설정 없이도 바로 사용할 수 있습니다.

onChanged 콜백의 단점

  • 제어 범위 한정: 입력된 텍스트 외의 복잡한 상태 변화를 감지하기 어려우며, 고급 기능이 필요한 경우에는 다소 한정적입니다.

2. TextEditingController 사용하기

좀 더 정교한 제어가 필요할 때는 TextEditingController를 사용합니다. TextEditingController는 텍스트 필드의 상태를 지속적으로 추적할 수 있어, 텍스트 변화를 감지하고 다른 로직을 실행하기에 적합합니다. 다음과 같은 단계로 설정할 수 있습니다.

단계 1: TextEditingController 생성

TextEditingController를 생성하고 이를 상태로 관리합니다.

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

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

class _MyCustomFormState extends State<MyCustomForm> {
  final myController = TextEditingController();

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

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: myController,
    );
  }
}

주의: TextEditingController는 상태가 사라질 때 반드시 dispose() 메서드를 호출하여 정리해야 합니다. 이를 통해 메모리 누수를 방지할 수 있습니다.

단계 2: TextEditingController를 텍스트 필드에 연결하기

이제 생성된 TextEditingControllerTextFieldcontroller 속성에 연결합니다. 이를 통해 텍스트 필드의 내용을 추적하고 조작할 수 있습니다.

TextField(
  controller: myController,
),

단계 3: 변경 사항을 감지하고 함수 실행하기

텍스트 변경 시 호출될 함수를 정의하고, TextEditingControlleraddListener() 메서드를 사용해 이 함수를 연결합니다.

void _printLatestValue() {
  print('변경된 텍스트: ${myController.text} (길이: ${myController.text.characters.length})');
}

@override
void initState() {
  super.initState();
  myController.addListener(_printLatestValue);
}

이제 사용자가 텍스트 필드에 입력할 때마다 _printLatestValue 함수가 호출되어 최신 텍스트와 그 길이를 출력하게 됩니다.

TextEditingController의 장점

  • 강력한 제어: 텍스트 필드의 변화를 정교하게 추적하고 필요한 로직을 실행할 수 있습니다.
  • 유연한 기능 구현: 텍스트 필드의 상태를 쉽게 가져와 다른 로직에 활용할 수 있습니다.

TextEditingController의 단점

  • 설정의 복잡성: onChanged 콜백에 비해 설정이 다소 복잡합니다.
  • 메모리 관리 필요: 컨트롤러를 명시적으로 해제하지 않으면 메모리 누수가 발생할 수 있습니다.

Flutter에서 텍스트 필드 변경 감지의 활용 예시

검색 기능과 자동 완성

onChanged 콜백이나 TextEditingController를 사용하여 검색어가 입력될 때마다 필터링된 결과를 실시간으로 보여줄 수 있습니다. 사용자 입력에 맞춰 검색 결과를 업데이트하는 UX는 검색 기능에서 유용하게 적용됩니다.

입력 필터링과 유효성 검사

실시간으로 입력된 텍스트를 감지하여 조건에 맞지 않는 입력을 필터링하거나, 잘못된 입력이 있을 때 경고 메시지를 표시할 수 있습니다. 예를 들어, 이메일 형식이 올바른지 확인하거나 특정 문자열이 포함되었는지 감지할 때 유용합니다.

입력 제한 기능

사용자가 입력한 텍스트의 길이를 제한하거나 특정 조건을 만족하지 않으면 알림을 표시할 수 있습니다. 이를 통해 불필요한 입력 오류를 방지하고, 사용자에게 유효한 입력을 유도할 수 있습니다.


결론

Flutter에서 텍스트 필드 변경 사항을 감지하는 것은 앱의 동적 기능을 구현하는 데 필수적인 요소입니다. onChanged 콜백과 TextEditingController는 각각 장단점이 있어, 간단한 경우에는 onChanged를, 더 정교한 제어가 필요한 경우에는 TextEditingController를 선택하여 활용하는 것이 좋습니다. 실시간으로 반응하는 UI를 통해 사용자에게 향상된 경험을 제공하세요.

 

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(
      title: 'Retrieve Text Input',
      home: MyCustomForm(),
    );
  }
}

// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  @override
  State<MyCustomForm> createState() => _MyCustomFormState();
}

// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
  // Create a text controller and use it to retrieve the current value
  // of the TextField.
  final myController = TextEditingController();

  @override
  void initState() {
    super.initState();

    // Start listening to changes.
    myController.addListener(_printLatestValue);
  }

  @override
  void dispose() {
    // Clean up the controller when the widget is removed from the widget tree.
    // This also removes the _printLatestValue listener.
    myController.dispose();
    super.dispose();
  }

  void _printLatestValue() {
    final text = myController.text;
    print('Second text field: $text (${text.characters.length})');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Retrieve Text Input'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              onChanged: (text) {
                print('First text field: $text (${text.characters.length})');
              },
            ),
            TextField(
              controller: myController,
            ),
          ],
        ),
      ),
    );
  }
}