I need to realise row of TextField widgets. I did it. But now I want to get actual value TextEditingController from State my TextField. How I can do this?
Its my Stateful widget:
class _UnRecognition extends StatefulWidget {
final String text;
const _UnRecognition({Key? key, required this.text}) : super(key: key);
#override
State<StatefulWidget> createState() => _UnRecognitionState();
}
class _UnRecognitionState extends State<_UnRecognition> {
final TextEditingController editWordController = TextEditingController();
#override
void initState() {
editWordController.text = widget.text;
super.initState();
}
#override
Widget build(BuildContext context) {
return IntrinsicWidth(
child: TextField(
controller: editWordController,
),
);
}
Its, where i want to use my variable:
void _SomeMethodOutsideWidget() {
for (var obj in ListOfWidget) {
if (obj is _UnRecognition) {
**this I get access obj.editWordController.text**
}
}
}
you need to learn the providers: https://pub.dev/packages/provider
basically it is used to retrieve values in the contexts of Statefull Widgets
you can find many tutorial of state managment provider on youtube
I am trying to make a custom stateful widget work in flutter. It is called LetterTextForm, and contains some layout customization and a TextField.
I want it to be easy to retrieve the text that is entered into the TextField of the widget, with some kind of getter method in the widget body that returns TextEditingController.text. The problem is, when I make the TextEditingController controller a final variable like this:
class LetterTextForm extends StatefulWidget {
LetterTextForm({#required this.label, this.prefixIcon});
final Widget prefixIcon;
final String label;
final TextEditingController controller = TextEditingController();
String get textEntered => controller.text;
#override
_LetterTextFormState createState() => _LetterTextFormState();
}
class _LetterTextFormState extends State<LetterTextForm> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
labelText: widget.label,
)
);
}
}
the following error is thrown:
"A TextEditingController was used after being disposed."
I thought maybe the problem was that the controller shouldn't be final, so I moved the controller into the state class and made a function in the state class, textCallback, that modifies a non-final String that is in the widget class. This way works but violates widget immutability:
class LetterTextForm extends StatefulWidget {
LetterTextForm({#required this.label, this.prefixIcon});
final Widget prefixIcon;
final String label;
String text = '';
String get textEntered => text;
#override
_LetterTextFormState createState() => _LetterTextFormState();
}
class _LetterTextFormState extends State<LetterTextForm> {
var controller = TextEditingController();
void textCallback() {
widget.text = controller.text;
}
#override
void initState() {
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
labelText: widget.label,
)
);
}
}
Is there a way to do this that both 1) doesn't violate immutability and 2) doesn't dispose of the TextEditingController() immediately when the user hits Enter?
It works when I get rid of the dispose() method, but that seems like it could cause some performance/memory efficiency issues later. Maybe I don't know what I'm talking about there...
Perhaps I should try something different entirely and place the TextEditingControllers further up in the tree with the ancestors of the LetterTextForm widget, and not internally within the widget?
Create a ValueChanged<String> callback member and call it back when data is completed.
class LetterTextForm{
...
final ValueChanged<String> onChanged;
...
}
class _LetterTextFormState extends ... {
final _controller = TextEditingController();
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(...) {
return TextField(
controller: _controller,
onSubmitted: (value) {
if (widget.onChanged != null) {
widget.onChanged(value); // returns entered value to caller
}
},
);
}
}
P.S. If to use TextFormField then controller is not necessary and you can use initialData for initialization.
I am using provider for state management. I am in a situation where there are multiple types of fields in my form. The problem is with text-field
Whenever I change Text it is behaving weirdly like the text entered is displayed in reverse order.
class MyProvider with ChangeNotifier{
String _name;
String get name => _name;
setname(String name) {
_name = name;
notifyListeners();
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyProvider myProvider = Provider.of<MyProvider>(context);
final TextEditingController _nameController = TextEditingController(
text: myProvider.name,
);
return TextField(
controller: _nameController,
onChanged: myProvider.setname,
);
}
It happens because new instance of TextEditingController is created on every widget build, and information about current cursor position (TextEditingValue) is getting lost.
Create a controller once in initState method and dispose of it in dispose method.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
TextEditingController _nameController;
#override
void initState() {
final MyProvider myProvider = Provider.of<MyProvider>(context, listen: false);
super.initState();
_nameController = TextEditingController(text: myProvider.name);
}
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final MyProvider myProvider = Provider.of<MyProvider>(context);
return TextField(
controller: _nameController,
onChanged: myProvider.setname,
);
}
}
To store the text from the TextField into Provider you need to send the text property from the controller to the provider:
_nameController.addListener(() {
myProvider.setName(_nameController.text);
});
This would also remove the problem you are getting the reverse text in the TextField
I have problems understanding how to pass input information from a extended child widget back to its parent widget. I tried to create a setter in the parent widget but i can not call it through the "ParentWidget.of(context).setterName".
What is the best way to pass the user input information from a child widget back to a parent?
Thanks for your help
class SignInMobilePortrait extends StatefulWidget {
#override
_SignInMobilePortraitState createState() => _SignInMobilePortraitState();
}
class _SignInMobilePortraitState extends State<SignInMobilePortrait> {
//text field state
String _email = '';
set email(String value) => setState(() => _email = value);
...
LoginCredentials(),
...
}
class LoginCredentials extends StatelessWidget {
LoginCredentials({
Key key,
}) : super(key: key);
Widget build(BuildContext context) {
return Container(
...
onChanged: (value) {
//CHANGE EMAIL VALUE HERE;
},
...
}
A correct way to do this is to pass TextEditingController from SignInMobilePortrait down to LoginCredentials:
class _SignInMobilePortraitState extends State<SignInMobilePortrait> {
final _emailController = TextEditingController();
LoginCredentials(_emailController),
...
// Access _emailController.text somewhere
}
class LoginCredentials extends StatelessWidget {
final TextEditingController controller;
LoginCredentials({
Key key,
#required this.controller,
}) : super(key: key);
Widget build(BuildContext context) {
return TextField(
...
controller: controller,
}
Given a stateful widget, is somehow possible to call a method defined in the State class (the one which extends State<NameOfTheWidget>). Actually, I just want to rebuild the _State class, like calling setState() but from outside of the class. I know how to it from children to parents but not viceversa.
class Foo extends StatefulWidget{
State createState() => new _State();
//...bar() ??
}
class _State extends State<Foo>{
#override
Widget build(BuildContext context) {...}
void bar(){...}
}
EDIT: some real code
First, we hace the equivalent to the inner widget; it's a a customized text field. The point is that I want enable and disable it according to the boolean _activo variable.
import 'package:flutter/material.dart';
import 'package:bukit/widgets/ensure.dart';
class EntradaDatos extends StatelessWidget{
final String _titulo;
final String _hint;
TextEditingController _tec;
FocusNode _fn = new FocusNode();
final String Function(String s) _validador;
final TextInputType _tit;
bool _activo;
/*
* CONSTRUCTOR
*/
EntradaDatos(this._titulo, this._hint, this._validador, this._tit, this._activo){
_tec = new TextEditingController();
}
#override
Widget build(BuildContext context){
print('Construyendo');
return new EnsureVisibleWhenFocused(
focusNode: _fn,
child: new TextFormField(
enabled: _activo,
keyboardType: _tit,
validator: _validador,
autovalidate: true,
focusNode: _fn,
controller: _tec,
decoration: InputDecoration(
labelText: _titulo,
hintText: _hint
),
)
);
}
String getContenido(){
return _tec.text;
}
}
Then I have a concrete implementation of the previous text field, which just extends it:
import 'package:flutter/material.dart';
import 'package:bukit/widgets/entrada_datos.dart';
class EntradaMail extends EntradaDatos{
static String _hint = "nombre#dominio.es";
static String _validador(String s){
if(s.isEmpty){
return 'El campo es obligatorio';
}else{
if(!s.contains('#') || !s.contains('.') || s.contains(' ')){
return 'Introduce una dirección válida';
}else{
String nombre = s.substring(0, s.indexOf('#'));
String servidor = s.substring(s.indexOf('#')+1, s.lastIndexOf('.'));
String dominio = s.substring(s.lastIndexOf('.')+1);
if(nombre.length < 2 || servidor.length < 2 || dominio.length < 2){
return 'Introduce una dirección válida';
}
}
}
}
EntradaMail(String titulo, bool activo) : super(titulo, _hint, _validador, TextInputType.emailAddress, activo);
}
Finally, the equivalent of my outter widget. It's just a checkbox followed by the prevoius EntradaEmail widget. As far as I know, once the checkbox is pressed and the onChange call is made, the setState call should rebuild everything, but I've contrasted with debug messaged that the build method of the first inner widget is never called. My point is enabling and disabling the text field according to the checkbox.
class CampoEnvio extends StatefulWidget{
EntradaMail _mail;
EntradaMovil _movil;
String _tituloMail;
String _tituloMovil;
bool _usaMail = false;
bool _usaMovil = false;
CampoEnvio(this._tituloMail, this._tituloMovil){
_mail = new EntradaMail(_tituloMail, _usaMail);
_movil = new EntradaMovil(_tituloMovil, _usaMovil);
}
State createState() => _State(_mail, _movil, _usaMail, _usaMovil, _tituloMail, _tituloMovil);
}
class _State extends State<CampoEnvio>{
bool _usaMail;
bool _usaMovil;
String _tituloMail;
String _tituloMovil;
EntradaMail _mail;
EntradaMovil _movil;
_State(this._mail, this._movil, this._usaMail, this._usaMovil, this._tituloMail, this._tituloMovil);
#override
Widget build(BuildContext context){
return new Column(
children: <Widget>[
new ListTile(
leading: new SizedBox(
width: 70.0,
child: new Row(
children: <Widget>[
new Checkbox(
value: _usaMail,
activeColor: Colors.black,
onChanged: (value) {
setState(() {
_usaMail = value;
});
},
),
],
),
),
title: _mail,
),
//...
new Divider()
],
);
}
}
Yes, in theory it is possible using a GlobalKey, but not recommended!
class OuterWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => OuterWidgetState();
}
class OuterWidgetState extends State<OuterWidget> {
final _innerKey = GlobalKey<InnerWidgetState>();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
InnerWidget(key: _innerKey),
RaisedButton(
child: Text('call foo'),
onPressed: () {
_innerKey.currentState.foo();
},
)
],
);
}
}
class InnerWidget extends StatefulWidget {
InnerWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => InnerWidgetState();
}
class InnerWidgetState extends State<InnerWidget> {
String _value = 'not foo';
#override
Widget build(BuildContext context) {
return Text(_value);
}
void foo() {
setState(() {
_value = 'totally foo';
});
}
}
Better approach: Instead, what it would be a good idea to pull the state up:
class OuterWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => OuterWidgetState();
}
class OuterWidgetState extends State<OuterWidget> {
String _innerValue;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
InnerWidget(value: _innerValue),
RaisedButton(
child: Text('call foo'),
onPressed: () {
setState(() {
_innerValue = 'totally foo';
});
},
)
],
);
}
}
class InnerWidget extends StatefulWidget {
InnerWidget({Key key, this.value}) : super(key: key);
final String value;
#override
State<StatefulWidget> createState() => InnerWidgetState();
}
class InnerWidgetState extends State<InnerWidget> {
#override
Widget build(BuildContext context) {
return Text(widget.value);
}
}
If you can, make the inner widget stateless:
class InnerWidget extends StatelessWidget {
InnerWidget({Key key, this.value}) : super(key: key);
final String value;
#override
Widget build(BuildContext context) {
return Text(value);
}
}
If your child is interactive (taps, checkbox...), you can define callbacks with VoidCallback or ValueChanged<T> (or your own typedef) to process the events in the parent widget.
Ok, now that you added the sample code, I will try to explain why your widget does not work, and I will try to explain what other improvements can be made.
First of all, you can improve the readability of your code by using named constructors for all of your widgets, like in my other answers (You can auto-generate them with Android Studio: Define some final fields, then press the lightbulb button to generate the constructor).
The next problem is that widgets which create a TextEditingController must always be stateful widgets! Otherwise the input made by the user will disappear after every build!
Usually you would pass in the TextEditingController from a parent widget (the widget that handles processes data when you submit it)
Also, it is discouraged to extend widgets. Instead, use composition, e.g.:
class EntradaMail extends StatelessWidget {
final String titulo;
// ...
Widget build(BuildContext context) {
return EntradaDatos(
titulo: titulo,
//...
)
}
}
Widget properties should always be public and final (never start with a _).
You are doing some strange things in CampoEnvio.
First of all, you are for some reason passing in all the properties of the widget to the State in createState. That has some consequences which you probably don't intend.
In general it is extremely rare that your State class has constructor parameters, and usually you would not pass properties from the stateful widget to the state.
The problem is that createState is only called once, it is not called again when you call initState in a parent widget. The state is kept until the widget is disposed.
That means your state constructor is only called once as well, and the fields in _State (of CampoEnvio) will stay the same all the time. Even when the parent is rebuilt and calls the constructor of CampoEnvio again, the old values in _State will not be replaced.
It's also very stange that you are creating widgets (EntradaMail and EntradaMovil) in the StatefulWidget.
The class that extends StatefulWidget should not do that! It is basically just a "bag" of properties.
Here is the complete fixed sample code, following the conventions explained above:
class EntradaDatos extends StatefulWidget {
EntradaDatos({Key key, this.titulo, this.hint, this.validador, this.tit, this.activo}) : super(key: key);
final String titulo;
final String hint;
final String Function(String s) validador;
final TextInputType tit;
final bool activo;
#override
State<StatefulWidget> createState() => _EntradaDatosState();
}
class _EntradaDatosState extends State<EntradaDatos> {
// FocusNode and TextEditingController must be the same for the whole lifetime of the widget
// => put into State
TextEditingController _tec;
FocusNode _fn;
#override
void initState() {
super.initState();
_tec = new TextEditingController();
_fn = new FocusNode();
}
#override
Widget build(BuildContext context) {
print('Construyendo');
return new EnsureVisibleWhenFocused(
focusNode: _fn,
child: new TextFormField(
enabled: widget.activo,
keyboardType: widget.tit,
validator: widget.validador,
autovalidate: true,
focusNode: _fn,
controller: _tec,
decoration: InputDecoration(labelText: widget.titulo, hintText: widget.hint),
));
}
String getContenido() {
return _tec.text;
}
}
class EntradaMail extends StatelessWidget {
static String _hint = "nombre#dominio.es";
static String _validador(String s) {
if (s.isEmpty) {
return 'El campo es obligatorio';
} else {
if (!s.contains('#') || !s.contains('.') || s.contains(' ')) {
return 'Introduce una dirección válida';
} else {
String nombre = s.substring(0, s.indexOf('#'));
String servidor = s.substring(s.indexOf('#') + 1, s.lastIndexOf('.'));
String dominio = s.substring(s.lastIndexOf('.') + 1);
if (nombre.length < 2 || servidor.length < 2 || dominio.length < 2) {
return 'Introduce una dirección válida';
}
}
}
}
EntradaMail({Key key, this.titulo, this.activo}) : super(key: key);
final String titulo;
final bool activo;
#override
Widget build(BuildContext context) {
// use composition instead of inheritance
return EntradaDatos(
titulo: titulo,
activo: activo,
validador: _validador,
hint: _hint,
tit: TextInputType.emailAddress,
);
}
}
class CampoEnvio extends StatefulWidget {
const CampoEnvio({Key key, this.tituloMail, this.tituloMovil}) : super(key: key);
final String tituloMail;
final String tituloMovil;
#override
State<StatefulWidget> createState() => new _CampoEnvioState();
}
class _CampoEnvioState extends State<CampoEnvio> {
// I guess these variables are modified here using setState
bool _usaMail;
bool _usaMovil;
#override
Widget build(BuildContext context) {
// just rebuild the widgets whenever build is called!
final mail = new EntradaMail(
titulo: widget.tituloMail,
activo: _usaMail,
);
final movil = new EntradaMovil(
titulo: widget.tituloMovil,
activo: _usaMovil,
);
return new Column(
children: <Widget>[
new ListTile(
leading: new SizedBox(
width: 70.0,
child: new Row(
children: <Widget>[
new Checkbox(
value: _usaMail,
activeColor: Colors.black,
onChanged: (value) {
setState(() {
_usaMail = value;
});
},
),
],
),
),
title: mail,
),
//...
new Divider()
],
);
}
}
It always helps to look at the official samples in the Flutter repositories!