The TextField should be changed, if the String - Variable is changed from some other method.
The text box should therefore receive an update if the user enters a new text or the associated variable has been changed from another location.
//Calling new text boxes
new eingabeTextbox(false, "Bemerkungen", "...", (String str){zahlerBemerkungen = str; print("neuer Bemerkungswert:" + str);},zahlerBemerkungen),
//paged class to avoid source code redundancy
class eingabeTextbox extends StatelessWidget {
final bool _nummerischeTastatur;
final String _ueberschrift;
final String _platzhalter;
ValueChanged<String> eingegebenerWert;
ValueChanged<String> variableUeberwachen;
eingabeTextbox(this._nummerischeTastatur, this._ueberschrift, this._platzhalter, this.eingegebenerWert, this.variableUeberwachen);
#override
Widget build (BuildContext context){
return new TextField(
keyboardType: _nummerischeTastatur == true ? TextInputType.number : TextInputType.multiline,
decoration: new InputDecoration(
labelText: _ueberschrift,
hintText: _platzhalter
),
onSubmitted: eingegebenerWert,
onChanged: variableUeberwachen
);
}
}
My approach with "ValueChanged variableUeberwachen;" does not work.
Can someone help me?
Related
I made a custom input widget by wrapping Textfield in some widgets and so on. Now I can't reach the onChanged property directly from the custom widget. I tried making a property in my custom widget but couldn't implement it properly. I googled passing variables between widgets and it seems a hard thing to do. Any simple solution?
class Input extends StatelessWidget {
final String text;
final TextInputType type;
final int maxLength;
final bool password;
final String label;
final IconData icon;
final double padding;
final Function onChanged;
final ColorSwatch color;
Input(
{this.type = TextInputType.text,
#required this.text,
this.maxLength,
this.icon,
this.padding = 0.0,
this.password = false,
#required this.onChanged,
this.label,
this.color});
final String value = '';
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(3.0),
child: Text(
text + ":",
style: TextStyle(fontSize: 15.0, color: color),
),
),
Container(
padding: EdgeInsets.all(3.0),
width: 300.0,
child: TextField(
obscureText: password,
decoration: InputDecoration(
labelText: label,
icon: Icon(icon),
),
maxLength: maxLength,
keyboardType: type,
textInputAction: TextInputAction.next,
onChanged: onChanged,
),
),
],
),
);
}
}
You need to provide onChanged and other events in your custom widget, even if you are just providing these to the underlying TextField. In other words, you need to pass the onChanged function down through your custom widget.
For example:
MyAwesomeTextField extends StatelessWidget {
/// Callback for being notified of changes to the text field
/// This should have a proper type, I'm just using Function for simplicity
final Function onChanged;
// Make the onChanged property settable through the constructor
MyAwesomeTextField({this.onChanged});
Widget build(BuildContext context) {
// Construct your widget tree, and pass your onChanged function through
return TextField(onChanged: this.onChanged);
}
}
Then when you're using it, your custom widget will have an onChanged event:
...
MyCustomWidget(onChanged: (value) => print(value) )
...
Here is my code, I've created an ExpansionTile and it has a child TextFormField.
import 'package:flutter/material.dart';
class OrderCreatePage extends StatefulWidget {
#override
_OrderCreatePageState createState() => _OrderCreatePageState();
}
class _OrderCreatePageState extends State<OrderCreatePage> {
String _userID;
TextEditingController _controllerl;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("Create"),
),
body: ExpansionTile(
title: Text("Create"),
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "User ID",
icon: Icon(Icons.face),
),
validator: (val) {},
controller: _controllerl,
onSaved: (val) => _userID = val,
)
],
),
);
}
}
Whenever I type something into the TextFormField and collapse the ExpansionTile, the data in the TextFormField is lost. I'm using this type of UI because I have to create a big form getting a lot of details. If there is no ExpansionTile, then the user has to scroll a long way.
As mentioned by - #pskink
In your Code Add under TextEditingController _controllerl; :
#override
void initState() {
super.initState();
_controllerl = TextEditingController();
}
So your Code will look like :
class _OrderCreatePageState extends State<OrderCreatePage> {
String _userID;
TextEditingController _controllerl;
#override
void initState() {
super.initState();
_controllerl = TextEditingController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
....
......
https://api.flutter.dev/flutter/material/ExpansionTile/maintainState.html
ExpansionTile(maintainState: true, title: Text("text here"), ...
With this, the state of the children will be maintained after collapsing and opening.
I assume you use some sort of ScrollView to display the input fields.
The following is stated in the ExpansionTile reference:
This widget is typically used with ListView to create an "expand /
collapse" list entry. When used with scrolling widgets like ListView,
a unique PageStorageKey must be specified to enable the ExpansionTile
to save and restore its expanded state when it is scrolled in and out
of view.
So adding a PageStorageKey should do the trick:
ExpansionTile(
key: PageStorageKey('myInputField'),
title: Text("Create"),
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "User ID",
icon: Icon(Icons.face),
),
validator: (val) {},
controller: _controllerl,
onSaved: (val) => _userID = val,
)
],
)
The following code builds and runs as intended--when a user types something, an error message gets shown until the string passes an email validation format.
Widget emailField(){
return StreamBuilder(
stream: bloc.emailStream,
builder: (BuildContext context, snapshot) {
return TextField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'E-mail address',
errorText: snapshot.error
),
onChanged: (newValue){
bloc.updateEmail(newValue);
},
);
},
);
}
I am told that when the stream changes, the builder field gets called which rebuilds the TextField. But if that is the case, shouldn't the TextField always have a blank string? What happens instead is it retains its value.
I am trying to understand what exactly is happening here. Thanks!
Not really, If you look at the code of the TextField , you'll find it's a StatefulWidget, so its has a State and the state keep the value.
class TextField extends StatefulWidget
Additionally you can use the TextEditingController to handle(get/clear/set) the data of the TextField , if you don't provide a TextEditingController it will be created by default as you can see in the source code.
#override
void initState() {
super.initState();
if (widget.controller == null)
_controller = TextEditingController();
}
You can find more information here: https://flutter.io/cookbook/forms/text-field-changes/
TextField widget is newly created, but it's corresponding element is reused/recyled (and so is it state).
Here's a pretty good explanation from the creator: https://youtu.be/AqCMFXEmf3w?t=99
I have something like this. I am having difficulty understanding this error.
Why does accessing filterController here give this error here, but it doesn't give this error if I move the current entire TextFormField creation (between comments A and B) inside the build method? How does moving the entire TextFormField inside the build method make filterController static then and resolve this issue?
class AppHomeState extends State<AppHome> with SingleTickerProviderStateMixin
{
TabController _tabController;
final filterController = new TextEditingController(text: "Search");
//----A
TextFormField email = new TextFormField(
keyboardType: TextInputType.emailAddress,
controller: filterController, ------>ERROR : Error: Only static members can be accessed in initializers
);
//----B
#override
Widget build(BuildContext context)
{
return new Scaffold(
appBar: new AppBar(..),
);
}
}
How can I resolve this issue?
class AppHomeState extends State<AppHome> with SingleTickerProviderStateMixin {
TabController _tabController;
final filterController = new TextEditingController(text: "Search");
TextFormField email = ...
... is an initializer and there is no way to access this at this point.
Initializers are executed before the constructor, but this is only allowed to be accessed after the call to the super constructor (implicit in your example) was completed.
Therefore only in the constructor body (or later) access to this is allowed.
This is why you get the error message:
controller: filterController,
accesses this.filterController (this is implicit if you don't write it explicit).
To work around your issue (assuming email needs to be final) you can use a factory constructor and a constructor initializer list:
class AppHomeState extends State<AppHome> with SingleTickerProviderStateMixin {
factory SingleTickerProviderStateMixin() =>
new SingleTickerProviderStateMixin._(new TextEditingController(text: "Search"));
SingleTickerProviderStateMixin._(TextEditingController textEditingController) :
this.filterController = textEditingController,
this.email = new TextFormField(
keyboardType: TextInputType.emailAddress,
controller: textEditingController);
TabController _tabController;
final filterController;
final TextFormField email;
or when the email field does not need to be final email can be initialized in the constructor initializer list:
class AppHomeState extends State<AppHome> with SingleTickerProviderStateMixin {
SingleTickerProviderStateMixin() {
email = new TextFormField(
keyboardType: TextInputType.emailAddress,
controller: filterController,
);
}
TabController _tabController;
final filterController = new TextEditingController(text: "Search");
TextFormField email;
but in Flutter widgets initState is usually used for that
class AppHomeState extends State<AppHome> with SingleTickerProviderStateMixin {
#override
void initState() {
super.initState();
email = new TextFormField(
keyboardType: TextInputType.emailAddress,
controller: filterController,
);
}
TabController _tabController;
final filterController = new TextEditingController(text: "Search");
TextFormField email;
You can keep that as a method:
Widget getEmailController(){
return new TextFormField(
keyboardType: TextInputType.emailAddress,
controller: filterController,
);
}
and use it in UI:
body: Container(
child: getEmailController();
)
You can convert this variable to a function and you can take context in this function parameters.
Example
Widget myDialog (BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
body: new Center(
child: new Column(
children: <Widget>[
new Text("Invalid Username/Password"),
new Text("Please verify your login credentials"),
new RaisedButton(
child: new Text("Ok"),
onPressed:() {
Navigator.pop(context);//Error : Only static members can be accessed in initializers
}
),
],
),
)
);
}
// Using if you are doing in a class
this.myDialog(context);
// Using if you are using a global function
myDialog(context);
But, i think you want to show a error message. So, you can do it with a dialog not an page. It's more efficient because you can specify your dialog box with buttons or messages and you can use this error dialog everywhere. Let's look my global helper function for showing error messages.
void showError(BuildContext context, String error) {
showSnackBar(
context,
new Text(
'Error',
style: new TextStyle(color: Theme.of(context).errorColor),
),
content: new SingleChildScrollView(
child: new Text(error)
),
actions: <Widget>[
new FlatButton(
child: new Text(
'Ok',
style: new TextStyle(
color: Colors.white
),
),
onPressed: () {
Navigator.of(context).pop();
},
color: Theme.of(context).errorColor,
),
]
);
}
// Using in everywhere
showError(context, 'Sample Error');
I faced the same problem, and I was able to tackle the problem by setting the initial value of the TextFormField by adding the value I need to the controller's text, example:
_carPlateController.text = _initValues['carPlate'];
or
filterController.text = 'search';
I hope this helps! As it is an elegant easy solution for when using controllers.
I want to build a form where I have multiple TextField widgets, and want to have a button that composes and e-mail when pressed, by passing the data gathered from these fields.
For this, I started building an InheritedWidget to contain TextField-s, and based on the action passed in the constructor - functionality not yet included in the code below - it would return a different text from via toString method override.
As I understood, an InheritedWidget holds it's value as long as it is part of the current Widget tree (so, for example, if I navigate from the form it gets destroyed and the value is lost).
Here is how I built my TextForm using InheritedWidget:
class TextInheritedWidget extends InheritedWidget {
const TextInheritedWidget({
Key key,
this.text,
Widget child}) : super(key: key, child: child);
final String text;
#override
bool updateShouldNotify(TextInheritedWidget old) {
return text != old.text;
}
static TextInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(TextInheritedWidget);
}
}
class TextInputWidget extends StatefulWidget {
#override
createState() => new TextInputWidgetState();
}
class TextInputWidgetState extends State<TextInputWidget> {
String text;
TextEditingController textInputController = new TextEditingController();
#override
Widget build(BuildContext context) {
return new TextInheritedWidget(
text: text,
child: new TextField(
controller: textInputController,
decoration: new InputDecoration(
hintText: adoptionHintText
),
onChanged: (text) {
setState(() {
this.text = textInputController.text;
});
},
),
);
}
#override
String toString({DiagnosticLevel minLevel: DiagnosticLevel.debug}) {
// TODO: implement toString
return 'Név: ' + text;
}
}
And here is the button that launches the e-mail sending:
TextInputWidget nameInputWidget = new TextInputWidget();
TextInheritedWidget inherited = new TextInheritedWidget();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Örökbefogadás'),
),
body: new Container(
padding: const EdgeInsets.all(5.0),
child: new ListView(
children: <Widget>[
new Text('Név:', style: infoText16BlackBold,),
nameInputWidget,
new FlatButton(onPressed: () {
launchAdoptionEmail(nameInputWidget.toString(), 'kutya');
},
child: new Text('Jelentkezem'))
],
),
),
);
}
My problem is that the nameInputWidget.toString() simply returns TextInputWidget (class name) and I can't seem to find a way to access the TextInputWidgetState.toString() method.
I know that TextInheritedWidget holds the text value properly, but I'm not sure how I could access that via my nameInputWidget object.
Shouldn't the TextInputWidget be able to access the data via the context the InheritedWidget uses to determine which Widget to update and store the value of?
This is not possible. Only children of an InheritedWidget can access it's properties
The solution would be to have your InheritedWidget somewhere above your Button. But that imply you'd have to refactor to take this into account.
Following Rémi's remarks, I came up with a working solution, albeit I'm pretty sure it is not the best and not to be followed on a massive scale, but should work fine for a couple of fields.
The solution comes by handling all TextField widgets inside one single State, alongside the e-mail composition.
In order to achieve a relatively clean code, we can use a custom function that build an input field with the appropriate data label, which accepts two input parameters: a String and a TextEditingController.
The label is also used to determine which variable the setState() method will pass the newly submitted text.
Widget buildTextInputRow(var label, TextEditingController textEditingController) {
return new ListView(
shrinkWrap: true,
children: <Widget>[
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, top: 2.0, right: 5.0 ),
child: new Text(label, style: infoText16BlackBold)),
),
],
),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: new TextField(
controller: textEditingController,
decoration: new InputDecoration(hintText: adoptionHintText),
onChanged: (String str) {
setState(() {
switch(label) {
case 'Név':
tempName = 'Név: ' + textEditingController.text + '\r\n';
break;
case 'Kor':
tempAge = 'Kor: ' + textEditingController.text + '\r\n';
break;
case 'Cím':
tempAddress = 'Cím: ' + textEditingController.text + '\r\n';
break;
default:
break;
}
});
}
)),
),
],
)
],
);
}
The problem is obviously that you will need a new TextEditingController and a new String to store every new input you want the user to enter:
TextEditingController nameInputController = new TextEditingController();
var tempName;
TextEditingController ageInputController = new TextEditingController();
var tempAge;
TextEditingController addressInputController = new TextEditingController();
var tempAddress;
This will result in a lot of extra lines if you have a lot of fields, and you will also have to update the composeEmail() method accordingly, and the more fields you have, you will be more likely to forget a couple.
var emailBody;
composeEmail(){
emailBody = tempName + tempAge + tempAddress;
return emailBody;
}
Finally, it is time to build the form:
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Örökbefogadás'),
),
body: new ListView(
children: <Widget>[
buildTextInputRow('Név', nameInputController),
buildTextInputRow('Kor', ageInputController),
buildTextInputRow('Cím', addressInputController),
new FlatButton(onPressed: () { print(composeEmail()); }, child: new Text('test'))
],
),
);
}
For convenience, I just printed the e-mail body to the console while testing
I/flutter ( 9637): Név: Zoli
I/flutter ( 9637): Kor: 28
I/flutter ( 9637): Cím: Budapest
All this is handled in a single State.