원인
- 키보드가 올라오는 과정에서 플러터가 새롭게 UI를 렌더링함
- 위 과정에서 TextEditingController의 초기화가 의도된 흐름대로 진행되지 않아 텍스트 입력창에 초기 텍스트가 바인딩되지 않음
해결
- StatefulWidget을 사용할 때 didUpdateChange(Widget oldWidget) 함수를 오버라이드해서 사용해야함,
- 해당 함수는 위젯의 렌더링 과정에서 변화된 값을 감지하고 oldWidget을 파라미터로 전달해줌.
- 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: '-',
),
],
),
);
}
}