[Flutter] Form으로 감싸진 여러 TextFormField에서 키보드가 올라올 때 초기값이 사라지는 현상

Raon·2022년 5월 31일
0

Flutter

목록 보기
2/25
post-custom-banner

원인

  1. 키보드가 올라오는 과정에서 플러터가 새롭게 UI를 렌더링함
  2. 위 과정에서 TextEditingController의 초기화가 의도된 흐름대로 진행되지 않아 텍스트 입력창에 초기 텍스트가 바인딩되지 않음

해결

  1. StatefulWidget을 사용할 때 didUpdateChange(Widget oldWidget) 함수를 오버라이드해서 사용해야함,
  2. 해당 함수는 위젯의 렌더링 과정에서 변화된 값을 감지하고 oldWidget을 파라미터로 전달해줌.
  3. didUpdateChange함수를 통해 이전 위젯의 값을 가져옴으로서 초기화 문제를 해결
    • 텍스트를 사용자가 입력하는 과정은 렌더링을 하지 않으므로 데이터 입력 자체에 didUpdateChange함수가 간섭하는 일은 없을 것으로 생각된다.

전체 코드

class CustomInputWidget extends StatefulWidget {
  CustomInputWidget({
    Key? key,
    this.onChanged,
    this.initialValue,
    this.label,
  }) : super(key: key);

  final ValueChanged<String>? onChanged;
  final TextEditingController textController = TextEditingController();
  final dynamic initialValue;
  final String? label;

  @override
  State<CustomInputWidget> createState() => _CustomInputWidgetState();
}

class _CustomInputWidgetState extends State<CustomInputWidget> {
  bool suffixIconState = false;
  Timer? debounce;

  @override
  void initState() {
    widget.textController.text = widget.leadingText == null
        ? widget.initialValue.toString()
        : '${widget.leadingText} ${widget.initialValue.toString()}';

    suffixIconState = widget.initialValue.toString().isNotEmpty;
    super.initState();
  }
  
  @override
  void didUpdateWidget(covariant NptnInputWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    widget.textController.text = oldWidget.textController.text;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (widget.label != null)
          Column(
            children: [
              Text(
                '*${widget.label}',
                style: Theme.of(context).textTheme.bodyMedium,
              ),
              SizedBox(height: 4.0 * heightUnit),
            ],
          ),
        TextFormField(
          keyboardType: widget.keyboardType ?? TextInputType.none,
          textAlign: widget.textAlign ?? TextAlign.start,
          readOnly: widget.readOnly,
          controller: widget.textController,
          style: Theme.of(context).textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w400),
          onChanged: (value) => setState(() {
            if (num.tryParse(value) != null) {
              widget.textController.text = widget.leadingText == null
                  ? getThousandSeperator(int.tryParse(value))
                  : '${widget.leadingText} ${getThousandSeperator(int.tryParse(value))}';
            } else {
              widget.textController.text = widget.leadingText == null ? value : '${widget.leadingText} $value';
            }

            widget.textController.selection = TextSelection.collapsed(offset: widget.textController.text.length);
            suffixIconState = widget.textController.text.isNotEmpty;

            debounce = debouncer(debounce, () {
              widget.onChanged!(value);
            });
          }),
          decoration: InputDecoration(
            hintText: widget.hintText,
            hintStyle: Theme.of(context)
                .textTheme
                .bodyLarge!
                .copyWith(color: Theme.of(context).tabBarTheme.unselectedLabelColor, fontWeight: FontWeight.w400),
            contentPadding: EdgeInsets.symmetric(vertical: 10.0 * heightUnit, horizontal: 16.0 * widthUnit),
            focusedBorder: widget.readOnly
                ? OutlineInputBorder(
                    borderRadius: BorderRadius.circular(6.0),
                    borderSide: BorderSide(color: context.unFocusedColor),
                  )
                : OutlineInputBorder(
                    borderRadius: BorderRadius.circular(6.0),
                    borderSide: BorderSide(color: context.focusedColor),
                  ),
            enabledBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(6.0),
              borderSide: BorderSide(color: context.unFocusedColor),
            ),
            filled: true,
            fillColor: context.invoiceInputFillColor,
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(6.0),
            ),
            suffixIcon: !widget.readOnly
                ? Visibility(
                    visible: suffixIconState,
                    child: IconButton(
                      padding: EdgeInsets.only(
                        right: 2.0 * widthUnit,
                      ),
                      constraints: const BoxConstraints(),
                      onPressed: () => setState(() {
                        widget.textController.clear();
                        suffixIconState = widget.textController.text.isNotEmpty;
                      }),
                      icon: SvgPicture.asset(
                        'assets/icons/delete_all.svg',
                        color: Theme.of(context).iconTheme.color,
                      ),
                    ),
                  )
                : null,
          ),
        ),
      ],
    );
  }
}

class SomePage extends StatelessWidget {
	String id = '';
    String password = '';
    
	@override
    Widget build(BuildContext context) {
    	return Form(
        	key: controller.formKey,
            	child: Column(
                	children: [
          				CustomInputWidget(
                          onChanged: (val) => print(val), 
                          initialValue: controller.invoiceNo,
                          label: 'id'.tr,
                          hintText: '-',
                        ), 
                      CustomInputWidget(
                        onChanged: (val) => print(val), 
                        initialValue: controller.invoiceNo,
                        label: 'password'.tr,
                        hintText: '-',
                      ), 
        		],
      		),
    	);
  	}
}
profile
Flutter 개발자
post-custom-banner

0개의 댓글