Axel
Axel

Reputation: 5141

Flutter sets textfield cursor to start when changing controller's text

I have a reusable text field class like so:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String value = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: MyTextField(
            value: value,
            onChange: (val) {
              setState(() {
                value = val;
              });
            },
          ),
        ),
      ),
    );
  }
}

typedef ChangeCallback = void Function(String value);

class MyTextField extends StatefulWidget {
  final ChangeCallback onChange;
  final String value;

  const MyTextField({this.onChange = _myDefaultFunc, this.value = ""});
  
  static _myDefaultFunc(String value){}

  @override
  _MyTextFieldState createState() => _MyTextFieldState();
}

class _MyTextFieldState extends State<MyTextField> {
  final controller = TextEditingController();

  @override
  void initState() {
    controller.text = widget.value;
    super.initState();
  }

  @override
  void didUpdateWidget(covariant MyTextField oldWidget) {
    controller.text = widget.value;
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      onChanged: (value) {
        widget.onChange(value);
      },
    );
  }
}

As you can see, if the value is changed then onChange callback is called and also the value is again sent back to TextField. The problem with this is every time I update the value, TextField sets the cursor always to the start. Probably because the updated value is sent back to the TextField everytime? Not sure. Can you help me with this?

Upvotes: 2

Views: 698

Answers (2)

Javeed Ishaq
Javeed Ishaq

Reputation: 7105

No need to update values in didUpdateWidget

//@override
//   void didUpdateWidget(covariant MyTextField oldWidget) {
//     controller.text = widget.value;
//     super.didUpdateWidget(oldWidget);
//   }

I just commented-out this function and the code works fine below is the complete code

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String value = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(children: [
          SizedBox(height:50),
          Text("text input: $value"),
          SizedBox(height:50),
          Center(
            child: MyTextField(
              value: value,
              onChange: (val) {
                setState(() {
                  value = val;
                });
              },
            ),
          ),
        ]),
      ),
    );
  }
}

typedef ChangeCallback = void Function(String value);

class MyTextField extends StatefulWidget {
  final ChangeCallback onChange;
  final String value;

  const MyTextField({this.onChange = _myDefaultFunc, this.value = ""});

  static _myDefaultFunc(String value) {}

  @override
  _MyTextFieldState createState() => _MyTextFieldState();
}

class _MyTextFieldState extends State<MyTextField> {
  final controller = TextEditingController();

  @override
  void initState() {
    controller.text = widget.value;
    super.initState();
  }

//   @override
//   void didUpdateWidget(covariant MyTextField oldWidget) {
//     controller.text = widget.value;
//     super.didUpdateWidget(oldWidget);
//   }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      onChanged: (value) {
        widget.onChange(value);
      },
    );
  }
}

Upvotes: 1

dshukertjr
dshukertjr

Reputation: 18750

Instead of using onChanged method to get the value of text field, pass a TextEditingController.

class MyTextField extends StatefulWidget {
  final TextEditingController controller;
  final String defaultValue;

  const MyTextField({@required this.controller, this.defaultValue = ''});

  @override
  _MyTextFieldState createState() => _MyTextFieldState();
}

class _MyTextFieldState extends State<MyTextField> {
  @override
  void initState() {
    widget.controller.text = widget.defaultValue;
    super.initState();
  }

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

This way, you can declare a TextEditingController in the parent widget to get the value like you would normally do with Flutter's native TextFormField.

Upvotes: 1

Related Questions