9

I want to display something in a Text whenever a TextFields text changes:

class _MyPageState extends State<MyPage> {

String name;

@override
  Widget build(BuildContext context) {
      TextEditingController c = new TextEditingController(text: name);
      c.addListener(() {
         setState(() { name = c.text;});
      });
      return Scaffold(
         body: Center(
            child: Column(children: [ 
               Text('Hello, ' + name + '!'),
               TextField(controller: c)
      ])));
  }
}

The Text updates as exspected, but the problem is that the Cursor of the TextField moves to position 0 each time I enter a character.

julienduchow
  • 1,026
  • 1
  • 11
  • 29

3 Answers3

13

The issue is that you are creating a new TextEditingController each time your widget is rebuilt. The widget is being rebuilt on every character that is typed.

You just need to move the TextEditingController outside of the widget build function. And also move the c.addListener to the widget's initState function. This way the TextEditingController is only created one time, and the listener is only added one time.

PS: It is also good to dispose your controller when the widget is disposed

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  String name = '';
  TextEditingController c = new TextEditingController();

  @override
  void initState() {
    c.addListener(() {
      setState(() {
        name = c.text;
      });
    });
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child: Column(children: [
      Text('Hello, ' + name + '!'),
      TextField(controller: c)
    ])));
  }
}
mskolnick
  • 1,062
  • 8
  • 12
0

I have modified your code, you just need to change your TextEditingController see below.

class _MyPageState extends State<MyWidget> {
  // initialize to empty string
  String name = "";
  
 @override
  Widget build(BuildContext context) {
    // this is how textEditingController should be
    final TextEditingController c = TextEditingController.fromValue(
            new TextEditingValue(
                text: name,
                selection: new TextSelection.collapsed(
                    offset: name.length)));
    
    c.addListener(() {
         setState(() { name = c.text;});
      });
    
      return Scaffold(
         body: Center(
            child: Column(children: [ 
               Text('Hello, ' + name + '!'),
               TextField(controller: c)
      ])));
  }
}
0

in my case, i have to customize TextField style.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class ClearableTextField extends StatefulWidget {
  const ClearableTextField({
    Key? key,
    this.prefixText,
    this.suffixText,
    this.prefix,
    this.suffix,
    this.inputFormatters,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.hintText,
    this.margin,
    this.keyboardType,
    this.fontSize = 16,
    this.height = 1.5,
    this.maxLength,
    required this.value,
    required this.onChanged,
  }) : super(key: key);

  final String? prefixText;
  final String? suffixText;
  final Widget? prefix;
  final Widget? suffix;
  final List<TextInputFormatter>? inputFormatters;
  final CrossAxisAlignment crossAxisAlignment;
  final String? hintText;
  final EdgeInsetsGeometry? margin;
  final TextInputType? keyboardType;
  final double? fontSize;
  final double? height;
  final int? maxLength;
  final dynamic value;
  final ValueChanged<String> onChanged;

  @override
  State<ClearableTextField> createState() => _ClearableTextFieldState();
}

class _ClearableTextFieldState extends State<ClearableTextField> {
  Color _underlineColor = const Color(0xFFE2E5EF);
  bool _showClear = false;
  late TextEditingController _controller;
  final FocusNode _focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.value);
    _controller.addListener(_onTextChanged);
    if (_controller.text.isNotEmpty) _showClear = true;
    _focusNode.addListener(_handleFocusChange);
  }

  @override
  void didUpdateWidget(ClearableTextField oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.value == widget.value) return;
    // _controller.text = widget.value; // this would make the cursor weird
    _controller.value = TextEditingValue(
      text: widget.value,
      selection: TextSelection.collapsed(offset: widget.value.length),
    ); // this works fine
  }

  @override
  void dispose() {
    _controller.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  void _onTextChanged() {
    setState(() {
      _showClear = _controller.text.isNotEmpty;
    });
    widget.onChanged(_controller.text);
  }

  void _handleFocusChange() {
    if (!_focusNode.hasFocus) {
      FocusManager.instance.primaryFocus?.unfocus();
      _underlineColor = const Color(0xFFE2E5EF);
    } else {
      _underlineColor = Theme.of(context).primaryColor;
    }
    setState(() {});
  }

  void _clearText() {
    widget.onChanged('');
    setState(() {
      _controller.clear();
      _showClear = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: widget.margin,
      padding: EdgeInsets.symmetric(vertical: 5.w),
      decoration: BoxDecoration(
        border: Border(bottom: BorderSide(color: _underlineColor)),
      ),
      child: Row(
        crossAxisAlignment: widget.crossAxisAlignment,
        children: [
          if (widget.prefix != null || widget.prefixText != null) ...{
            if (widget.prefix != null) widget.prefix!,
            if (widget.prefixText != null) SizedBox(width: 8.w),
          },
          Expanded(
            child: TextField(
              controller: _controller,
              // or use this
              // _controller..selection =
              //     TextSelection.collapsed(offset: widget.value.length),
              focusNode: _focusNode,
              inputFormatters: widget.inputFormatters,
              keyboardType: widget.keyboardType,
              maxLength: widget.maxLength,
              style: TextStyle(
                fontWeight: FontWeight.w500,
                fontSize: widget.fontSize,
                height: widget.height,
              ),
              decoration: InputDecoration(
                counterText: '',
                contentPadding: EdgeInsets.zero,
                border: InputBorder.none,
                hintText: widget.hintText,
                hintStyle: TextStyle(
                  fontSize: 16.sp,
                  color: const Color(0xFF9498AB),
                  fontWeight: FontWeight.w400,
                ),
              ),
            ),
          ),
          if (_showClear)
            SizedBox(
              width: 32.w,
              height: 32.w,
              child: IconButton(
                iconSize: 18.w,
                icon: Image.asset('assets/images/public/clear_input.png'),
                onPressed: _clearText,
              ),
            ),
          if (widget.suffix != null || widget.suffixText != null) ...{
            if (widget.suffix != null) widget.suffix!,
            if (widget.suffixText != null)
              Text(
                widget.suffixText!,
                style: TextStyle(
                  fontSize: 24.sp,
                  fontWeight: FontWeight.w500,
                ),
              ),
          },
        ],
      ),
    );
  }
}

if you got didUpdateWidget, just be careful, use _controller.value instead of _controller.text, or just remove it.

QLJ球
  • 11
  • 1