Study Record

[Flutter] Form + TextFormField - 한번에 입력 값 관리하기, errorText, border 본문

Flutter/widget_TextField

[Flutter] Form + TextFormField - 한번에 입력 값 관리하기, errorText, border

초코초코초코 2023. 3. 3. 21:59
728x90

TextFormField Widget

TextFormField 위젯은 TextField 에서 몇개의 기능이 추가된 위젯이다. 따라서, TextField 에서 사용하는 인자값을 거의 그대로 사용할 수 있다. 또한, TextFormField 에는 사용자가 입력한 입력 값을 검증해 정해진 오류에 맞는 text를 보여줄 수 있다.(validator)

 

TextFormField 와 Form 위젯을 함께 사용하면 여러개의 입력값을 한번에 관리할 수 있다. 바로 예시를 들면,

Form+TextFormField.mp4
0.39MB

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(home: HomeScreen()));

class HomeScreen extends StatefulWidget {

  HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final GlobalKey<FormState> formKey = GlobalKey();
  String? text1 = "";
  String? text2 = "";
  String? text3 = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Form(
          key: formKey,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CustomTextFormField(onSavedEvent: (String? val) { text1 = val; }),
              CustomTextFormField(onSavedEvent: (String? val) { text2 = val; }),
              CustomTextFormField(onSavedEvent: (String? val) { text3 = val; }),
              ElevatedButton(
                onPressed: onCheckPress,
                child: const Text("check 버튼"),
              ),
              Text("$text1 , $text2 , $text3")
            ],
          ),
        ),
      ),
    );
  }

  void onCheckPress() {
    if (formKey.currentState == null) {
      return;
    }

    setState(() {
      if (formKey.currentState!.validate()) {
        // 오류가 없는 경우
        formKey.currentState!.save();
      } else {
        text1 = "";
        text2 = "";
        text3 = "";
      }
    });

  }
}

class CustomTextFormField extends StatelessWidget {
  final FormFieldSetter<String>? onSavedEvent;
  const CustomTextFormField({required this.onSavedEvent, Key? key})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: TextFormField(
        onSaved: onSavedEvent,
        validator: (String? val) {
          if (val == null || val.isEmpty) {
            return "값을 입력해주세요.";
          }
          if (val.contains("사과")) {
            return "\"사과\"는 금지어입니다.";
          }
          return null;
        },
        decoration: InputDecoration(
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(16.0),
            borderSide: BorderSide(width: 2, color: Colors.black87)
          ),
        ),
      )
    );
  }
}

 

예시 코드에 대해 설명하면,

Form 의 key 인자에 FormState 타입을 가진 GlobalKey 를 넣으면 이 키로 Form 의 자식 위젯의 TextFormField 를 전부 컨트롤할 수 있다.

 

 

😶 validate()

GlobalKey 의 currentState.validate(); 함수를 호출하면 키를 소유한 Form 위젯의 하위 TextFormField 위젯의 validator 를 전부 호출한다. 각 TextFormField 의 validator 의 리턴값이 전부 null 이면 true , 하나라도 null 이 아니면 false 이 리턴된다. 이 과정에서 TextFormField 는 validator 의 리턴값이 null 이 아니면 error 상태가 되고 return 값의 String 으로 errorText 가 보여진다.

 

final GlobalKey<FormState> formKey = GlobalKey();

// TextFormField 의 validator 를 호출하고 전부 다 null 을 리턴받으면 true 그렇지 않으면 false 을 리턴한다.
formKey.currentState!.validate();

// Form 예시
Form(
  key: formKey,
  child: Column(
    children: [
      TextFormField(
        validator: (String? val) {
          if (val == null || val.isEmpty) {
            return "값을 입력해주세요.";
          }
          return null;
        },
      ),
      TextFormField(
        validator: (String? val) {
          if (val == null || val.isEmpty) {
            return "값을 입력해주세요.";
          }
          return null;
        },
      ),
    ]
  ),
)

 

 

😶 save()

validate() 와 비슷하게 currentState.save(); 함수를 실행하면 관련된 TextFormField 전체의 onSaved 가 호출된다. 따라서 입력값을 한 번에 받아서 정리할 때 유용하다.

final GlobalKey<FormState> formKey = GlobalKey();
String? text1;
String? text2;

// TextFormField 의 onSaved 를 호출한다.
formKey.currentState!.save();

// 예시 Form
Form(
  key: formKey,
  child: Column(
    children: [
      TextFormField(
        onSaved: (String? val) {
          text1 = val;
        },
      ),
      TextFormField(
        onSaved: (String? val) {
          text2 = val;
        },
      ),
    ]
  ),
)

 

 

😶  validate() + save()

앞의 두 기능을 적절히 사용하면, validate() 로 한번에 TextFormField 의 입력값을 검증하고 true 를 반환하면(오류가 없으면) save() 를 통해 데이터를 한번에 가져오는 흐름이 가능하다.

final GlobalKey<FormState> formKey = GlobalKey();

// TextFormField 입력값 검증
if (formKey.currentState!.validate()) {
  // 오류가 없는 경우
  formKey.currentState!.save();
}

 

 

😶 validator 의 리턴값이 null이 아닐 경우 errorText, errorStyle

TextFormField 의 validator 이 호출되고 입력값에 오류가 있어 오류 관련 String 을 리턴할 경우 그 값이 errorText 로 보여지게 되는데 error 상태일 때, TextFormField 의 border 값과 textStyle 값을 InputDecoration 으로 설정할 수 있다.

TextFormField(
  ...
  decoration: InputDecoration(
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.black87)
    ),
    enabledBorder:  OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.black87)
    ),
    errorBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.pinkAccent)
  ),
  errorStyle: TextStyle(color: Colors.pinkAccent, fontWeight: FontWeight.bold)
);

 

border 값은 errorBorder 값으로 에러 텍스트 글자 스타일은 errorStyle 로 설정할 수 있다. 단, errorBorder 값이 null 아 아니면 border 값도 null 아 아니여야 한다. errorBorder != null && border == null 일 경우 error 상태일때 해당 errorBorder 로 바뀌지 않을 수 있다.

 

 

+ TextFormField 의 border 종류

인자 설명
border 기본 border
focusBorder focus 가 있는 상태
errorBorder error 상태에서 focus 가 없는 상태
focusErrorBorder error 상태에서 focus 가 있는 상태
enabledBorder 사용 가능 상태
disabledBorder 사용 불가 상태

 

TextFormField(
  decoration: InputDecoration(
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.black87)
    ),
    enabledBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.black87)
    ),
    focusedBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.deepPurple)
    ),
    focusedErrorBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.green)
    ),
    errorBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16.0),
      borderSide: BorderSide(width: 2, color: Colors.red)
    ),
  ),
);

 

 

😶 autovalidateMode

Form 의 인자 중 autovalidateMode : AutovalidateMode.always 로 하면 사용자가 입력 폼에 입력할 때마다 관련 입력 폼 전체 validate 를 호출한다.

Form(
  key: formKey,
  autovalidateMode: AutovalidateMode.always,
  child: Column(
    children: [
      TextFormField(
        onSaved: (String? val) {
          text1 = val;
        },
      ),
      TextFormField(
        onSaved: (String? val) {
          text2 = val;
        },
      ),
    ]
  ),
)
728x90