Reading values from a Flutter TextField - dart

I have a widget with a TextField that I'm initializing from a StreamBuilder, trying to use the bloc pattern. A Contact model is coming in through the stream. This is working to initially populate the TextField. My question is about reading the value after the user updates the TextField and then presses the Save button. How do I read the value from the TextField. I've included a simple example of what I'm trying to do.
void getTextValues() {
//???
}
#override
Widget build(BuildContext context) {
return StreamBuilder<Contact>(
stream: bloc.getContact,
builder: (context, contact) {
return Column(
children: <Widget>[
TextField(
controller: TextEditingController(text: contact.data.name),
),
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.blue,
onPressed: getTextValues,
child: new Text("Save"),
),
],
);
},
);
}
I think I could declare a TextEditingController and assign that to the controller property but I don't see a way to give that an initial value from the StreamBuilder. Am I missing something there?
TextEditingController nameController = TextEditingController();
....
controller: nameController,

I think I could declare a TextEditingController and assign that to the
controller property but I don't see a way to give that an initial
value from the StreamBuilder.
There is a way. Change your builder code to this:
builder: (context, contact) {
nameController.value = TextEditingValue(text: contact.data.name);
return Column(
children: <Widget>[
TextField(
controller: nameController,
),
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.blue,
onPressed: getTextValues,
child: new Text("Save"),
),
],
);
},

To assign TextEditingController a default value, use
TextEditingController _controller = TextEditingController(text: "Default value");
And to retrieve the value from a controller you can use
_controller.value

Related

Best practice for assigning key for different widgets?

I have been looking for controlling each widget in a page using a separate key. Can anyone suggest me best way for that?
I tried in Form like
final formKey = GlobalKey<FormState>();
Form(
key: formKey,
),
But I am confused while working with Container, Card, and others widget.
Can any suggest what should I follow and where?
You don't need formKey for Container, Card or any other widget which is not related with .
If you create Form Widget, you need to give it a TextFormField child, which inherits FormState.
The thing is here, probably you'll have multiple TextFormField widgets, so instead of creating and handling them one by one keys for each TextFormField, you create Form Widget to group your TextFormFields, then assign formKey once and use it at all.
Your formKey has no business with other type of widgets.
Explainer code:
Widget buildFormTree() {
final formKey = GlobalKey<FormState>();
String text1;
String text2;
return Column(
children: <Widget>[
Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
// key: asd, //No need
onSaved: (text) {
text1 = text;
},
),
TextFormField(
// key: qwe, // No need
onSaved: (text) {
text2 = text;
},
),
],
),
),
RaisedButton(
child: Text('Save Forms'),
onPressed: () {
///to trigger onSaved callback
formKey.currentState.save();
},
)
],
);
}

How expand text and container according text size?

I'm trying to create a card with a text within a container but I would like to show only a part of the text and when the user click on "show more", show the rest. I saw a Widget to construct text like this here, but I need expand the card container either and I don't know how to do that because I need to know how many lines the text have to expand with the correctly size. Exists a way to calculate the size according the number of lines or characters?
I tried to create the card as follows, where the DescriptionText is the Widget on the link and specify a minHeight in the Container in the hope of expanding the container along with the text but did not work.
Widget _showAnswerCard(Answer answer, User user) {
return Card(
elevation: 3.0,
color: Theme.of(context).backgroundColor,
child: Container(
constraints: BoxConstraints(minHeight: 90),
padding: EdgeInsets.all(10.0),
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(flex: 1, child: _showUserAvatar(answer)),
Expanded(flex: 3, child: _showAnswerDetails(answer, user)),
],
),
));
}
Widget _showAnswerDetails(Answer answer, User user) {
return Flex(
direction: Axis.vertical,
children: <Widget>[
Expanded(
flex: 3,
child: DescriptionTextWidget(text: answer.content),
),
Expanded(
flex: 1,
child: _showAnswerOptions(),
)
],
);
}
I'll really appreciate if someone could help me with that.
Just use Wrap widget to wrap your Card widget.
Based on your link for suggested answer. I did change to use Wrap widget.
Jus do copy/paste below code and check.
import 'package:flutter/material.dart';
class ProductDetailPage extends StatelessWidget {
final String description =
"Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Demo App"),
),
body: new Container(
child: new DescriptionTextWidget(text: description),
),
);
}
}
class DescriptionTextWidget extends StatefulWidget {
final String text;
DescriptionTextWidget({#required this.text});
#override
_DescriptionTextWidgetState createState() =>
new _DescriptionTextWidgetState();
}
class _DescriptionTextWidgetState extends State<DescriptionTextWidget> {
bool flag = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Wrap(
children: <Widget>[
Card(
margin: EdgeInsets.all(8),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: Column(
children: <Widget>[
Container(
child: Text(
widget.text,
overflow: flag ? TextOverflow.ellipsis : null,
style: TextStyle(
fontSize: 15,
),
),
),
InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
flag ? "show more" : "show less",
style: new TextStyle(color: Colors.blue),
),
],
),
onTap: () {
setState(() {
flag = !flag;
});
},
),
],
)),
),
],
);
}
}
Result:
The solution I can think of is to use two labels, one for displaying only one line of text and one for displaying all the text. When the button is clicked, the two labels are alternately displayed in an animated manner. There is no computer at the moment, it is not convenient to verify, I hope to give you some help in the implementation of the program.

State management in flutter

I've created a flutter app where I'm managing array for todolist in app. I've can add the text by add button.
I've created a widget to show in list.
My question is how am i supposed manage the UI of individual.
Code:
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {
_TodoListState createState() => new _TodoListState();
}
class _TodoListState extends State<TodoList> {
List _list = new List();
Widget listTile({String data: '[Empty data]'}) {
bool _writable = false;
TextEditingController _textController = new TextEditingController(text: data);
String _text = _textController.text;
if(!_writable){
return new Row(
children: <Widget>[
new Expanded(
child: new Text(data)
),
new IconButton(icon: new Icon(Icons.edit),
onPressed: () {
// setState(() {
_writable = ! _writable;
print(_writable.toString());
// });
}),
new IconButton(icon: new Icon(Icons.remove_circle), onPressed: null),
],
);
} else {
return new Row(
children: <Widget>[
new Expanded(
child: new TextField( controller: _textController )
),
new IconButton(icon: new Icon(Icons.done), onPressed: null),
],
);
}
}
void addInList(String string) {
print(string);
setState(() {
_list.add(string);
});
print(_list);
}
void removeFromList(int index){
}
static final TextEditingController _textController = new TextEditingController();
String get _text => _textController.text;
#override
Widget build(BuildContext context) {
Widget adderTile = new Row(
children: <Widget>[
new Expanded(
child:
new TextField(
textAlign: TextAlign.center,
controller: _textController ,
decoration: new InputDecoration( hintText: 'New item.!' ),
),
),
new IconButton(icon: new Icon(Icons.add), onPressed: (){addInList(_text);}),
],
);
return new MaterialApp(
title: 'TodoList',
home: new Scaffold(
appBar: new AppBar(title: new Text('TodoList'),),
body: new Column(
children: <Widget>[
adderTile,
new ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, int index){
return listTile(data: _list[index]);
}
),
],
),
),
);
}
}
if i change _writable inside setState then it rerenders widget and _writable becomes false again. if i do it without setState, then _writable becomes true but widget doesn't rerender.
P.S.: i don't want to add another array in to manage which is writable and which is not. Thanks in advance.
The variable
bool _writable = false;
is declared as local variable in the method listTile(), but should be moved next to List _list = new List(); to become a member variable. Then use setState() to set it and rebuild the view.
Edit:
You should create a dedicated StatefulWidget (TodoListEntry), having _writable as member as suggested above. Move almost the whole method body of listTile(...) to the build()-method of the TodoListEntryState, make the parameter String data also a member and pass the value via the constructor.

Dart / flutter: how to build a form with multiple user input fields easily

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.

TextFormField is losing focus - flutter

I am trying to solve form validation in my app. Every time I click to TextFromField, the focus is lost and keyboard hide. I found that problem is with "_formKey". But I need it to validation. How to solve it?
code snippet:
class _TodoCreateDetailPageState extends State<TodoPage> {
#override
Widget build(BuildContext context) {
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
String _title = widget.todo == null ? "New TODO" : widget.todo.title;
String _message;
return new Scaffold(
appBar: new AppBar(
title: new Text(_title),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save), onPressed: null),
body: new Padding(
padding: new EdgeInsets.all(20.0),
child: new Form(
key: _formKey,
child: new Column(
children: <Widget>[
new TextField(
decoration: new InputDecoration(labelText: 'Title'),
onChanged: (String value) {
_title = value;
},
),
new TextField(
decoration: new InputDecoration(labelText: 'Message'),
onChanged: (String value) {
_message = value;
},
)
],
))),
);
}
Change StatelessWidget to StatefulWidget works for me, as Derek Lakin said in comments.
this issue is due to GlobalKey Initialization with in build()
#override
Widget build(BuildContext context){
//here is the reason of losing focus.
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>()
}
Remove it from build and you are all good.
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
//this will work fine when you move key outside the build();
#override
Widget build(BuildContext context){
}
You seem to know that the problem is in the key itself, as in #6783. The solution is to avoid constructing your validation key every time. So, you may do it like this (or even make it a widget's property):
class _TodoCreateDetailPageState extends State<TodoPage> {
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
String _title = widget.todo == null ? "New TODO" : widget.todo.title;
String _message;
return new Scaffold(
appBar: new AppBar(
title: new Text(_title),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save), onPressed: null),
body: new Padding(
padding: new EdgeInsets.all(20.0),
child: new Form(
key: _formKey,
child: new Column(
children: <Widget>[
new TextField(
decoration: new InputDecoration(labelText: 'Title'),
onChanged: (String value) {
_title = value;
},
),
new TextField(
decoration: new InputDecoration(labelText: 'Message'),
onChanged: (String value) {
_message = value;
},
)
],
))),
);
}
I had the same problem, and found a solution for that, just define a controller variable outside the build method.
Use TextEditingController()
I think there's a much simpler solution that hasn't been mentioned yet and it has to do with the difference between GlobalKey and GlobalObjectKey.
A GlobalKey is only equal to itself and so each time your stateless widget is rebuilt, a new GlobalKey is created and the form is reset.
A GlobalObjectKey on the other hand is equal to any other GlobalObjectKey that has the same object:
GlobalObjectKey key1 = GlobalObjectKey('test');
GlobalObjectKey key2 = GlobalObjectKey('test');
key1 == key2; // True
So in this case, you should initialize your formKey as a GlobalObjectKey like this:
GlobalObjectKey<FormState> formKey = const GlobalObjectKey<FormState>('form');

Resources