I want to pass a variable name as a function parameter, but it doesn't seem to work : the content of my variable remains unchanged.
Widget Field(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
onChanged: (value) {
setState(() {
changedValue = value;
});
},
obscureText: isTextObscured,
);
}
Here, I want to change the value of the variable who has the name "changedValue". When I do it directly with the variable name, it works, but when I try to use the parameter, nothing happens. Here's an example of where I used it :
Widget LoginFields() {
return Column(
children: [
Field(email, Strings.emailLabel, false),
Field(password, Strings.passwordLabel, true),
ValidationButton(),
],
);
}
Thanks in advance!
There are many things to clarify here, like:
setState() is a method, that must be called inside a StatefullWidget.
if you create a function, name it with lowerCamelCase (effective dart).
for returning a Widget prefer extend a Widget, especially if you need a State.
if you seek a guide for TextField in Flutter - check cookbook here and here.
Here how you can set it up:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: Text('Material App Bar'),
),
body: Center(
child: Column(
children: [
FieldWidget(changedValueInitial: 'email', label: 'labelConstOne'),
FieldWidget(changedValueInitial: 'password', label: 'labelConstTwo', isTextObscured: true),
// ValidationButton(),
],
),
),
),
);
}
}
class FieldWidget extends StatefulWidget {
String changedValueInitial;
String label;
bool isTextObscured;
FieldWidget({
Key? key,
required this.changedValueInitial,
required this.label,
this.isTextObscured = false,
}) : super(key: key);
#override
_FieldWidgetState createState() => _FieldWidgetState();
}
class _FieldWidgetState extends State<FieldWidget> {
late String _changedValue;
#override
void initState() {
super.initState();
_changedValue = widget.changedValueInitial;
}
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(labelText: widget.label),
// validator: yourValidator,
initialValue: _changedValue,
onChanged: (value) {
setState(() {
_changedValue = value;
});
},
obscureText: widget.isTextObscured,
);
}
}
If that is what you need..
Related
I have problems following step by step what happens when onChanged is triggered on my TextField. Especially, I have a problem understanding where and why the variable value gets its actual value in the following example.
Example:
class felder extends StatefulWidget {
felder({super.key});
String textFieldName = "";
#override
State<felder> createState() => _felderState();
}
class _felderState extends State<felder> {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
obscureText: false,
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: 'Name'),
onChanged: (value) => widget.textFieldName = value,
)
],
);
}
}
How I always imagined it: I think flutter passes a function in the background, which has a parameter value, that has the content of the TextField.
Actually TextField is a widget that has its own state.
Whenever user types something, the value in a TextField
changes.
At that time, a callback is fired from the TextField.
The changed value is also passed along with the
callback.
Using onChanged: (value){ print(value); } , we can
get the value from that callback and use it as per our needs.
From TextField source code,
The text field calls the [onChanged] callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the [onSubmitted] callback.
To get the value from a TextField, you can also use TexteditingController.
First declare TextEditingController controller = TextEditingController();.
Then inside your TextField, add the controller like this
TextField(
controller: controller,
),
Then to get the value from controller, you can use controller.value.text.
What is a callback?
From GeeksForGeeks:
Callback is basically a function or a method that we pass as an
argument into another function or a method to perform an action. In
the simplest words, we can say that Callback or VoidCallback are used
while sending data from one method to another and vice-versa
Creating a callback
To create your own callback, you can use ValueChanged.
Code example:
Let's create our own button, that when the onChanged is called, it will give us a new value:
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
Usage:
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
Complete example
You can run/paste this example in your editor, and take a look:
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
See also
How to pass callback in Flutter
What's in onChanged Docs ?
ValueChanged<String>? onChanged
onChanged is of type ValueChanged<String> and is called when the user initiates a change to the TextField's value: when they have inserted or deleted text.
This callback doesn't run when the TextField's text is changed programmatically, via the TextField's controller. Typically it isn't necessary to be notified of such changes, since they're initiated by the app itself.
What is Callback ?
If we go by definition, the Callback is a function or a method which we pass as an argument into another function or method and can perform an action when we require it.
For Example, if you are working in any app and if you want any change in any value then what would you do?
Here you are in a dilemma that what you want to change either state() or a simple value/values. If you need to change states then you have various state-changing techniques but if you want to change simple values then you will use Callback.
Refer this article to understand the callback on event of textChange this will surely make you understand the core behind the mechanism
i have a textfield that require user to call another user using # sign. So if the user press # there should be a popup at the top of the textfield.
i have try using this package but if does not suit what i want...
here is an example.
it should only show when user type # only....
With the TextField you use it has the onChanged method to catch all the changes made to the text field input and with that you can evaluate the first character of the text field and that's what I'd recommend you to.
With my example, it prints every change made in the input of the textfield. It also checks only the first character of the input and prints to the console. You also can use setState instead of print to for example sets a boolean and then handles the Plugin you use.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late TextEditingController _controller;
#override
void initState() {
super.initState();
_controller = TextEditingController();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextField(
controller: _controller,
onChanged: (String value) {
value.characters.first == '#'
? print(
'Contains #',
)
: print(
'Does not contain #',
);
},
),
),
);
}
}
In flutter_typeahead package, you can add your logic in the suggestionsCallback.
In the example below, i am only calling the api and showing the data if the text starts with #. Modify as per your requirements.
TypeAheadField(
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
style: DefaultTextStyle.of(context).style.copyWith(
fontStyle: FontStyle.italic
),
decoration: InputDecoration(
border: OutlineInputBorder()
)
),
suggestionsCallback: (pattern) async {
if("${pattern}".startsWith("#")){
return await BackendService.getSuggestions(pattern);
}else{
return [];
}
},
itemBuilder: (context, suggestion) {
return ListTile(
leading: Icon(Icons.shopping_cart),
title: Text(suggestion['name']),
subtitle: Text('\$${suggestion['price']}'),
);
},
onSuggestionSelected: (suggestion) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProductPage(product: suggestion)
));
},
)
I want to autofill several textfields with one suggestion, like for example: If I select Washington as a state where I live I want the other field that would be country field to fill itself with U.S.
Thanks for your attention!
You will need to use setState( ) inside the onChanged. inside that setState, you will change the value of the other field otherDropdownValue . Here is a small example with dropDownMenus.
Dont forget you need a StatefulWidget (not StateLess)
Code:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String dropdownValue = 'One';
String otherDropdownValue = 'Two';
#override Widget build(BuildContext context) {
return Column(children: [
DropdownButton<String>(
value: dropdownValue,
onChanged: (String? newValue) {
//******************************************
//*****Here is what you are looking for*****
//******************************************
setState(() {
dropdownValue = newValue;
otherDropdownValue = newValue; ///Changes the other one
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value),);}).toList(),
),
DropdownButton<String>(
value: otherDropdownValue,
onChanged: (String? newValue) {
setState(() {
otherDropdownValue = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value),);}).toList(),
),
],
);
}
}
Let me know if this does not help?
EDIT to answer your last comment:
Same logic to apply with a TextField or a textformfield.
You will need to add a TextEditingController() to control the text displayed.
Below is a fully working example (the part you need to look at is at the end)
and here is a link that explains the code (note I adjusted the code for your specific use case)
https://flutter.dev/docs/cookbook/forms/text-field-changes
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
// Create a text controller and use it to retrieve the current value
// of the TextField.
final myController = TextEditingController();
#override
void initState() {
super.initState();
// Start listening to changes.
myController.addListener(_printLatestValue);
}
#override
void dispose() {
// Clean up the controller when the widget is removed from the widget tree.
// This also removes the _printLatestValue listener.
myController.dispose();
super.dispose();
}
void _printLatestValue() {
print('Second text field: ${myController.text}');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
///********************
///**** LOOK HERE ****
///********************
TextField(
onChanged: (text) {
myController.text = text;
},
),
TextField(
controller: myController,
),
],
),
),
);
}
}
I'm wondering if I can pass a function as a validator. I tried, but got no results.
Widget Field(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
);
}
checkFieldEmpty(String fieldContent) {
if(fieldContent.isEmpty) {
return 'Ce champ est obligatoire.';
}
return null;
}
Perhaps the function return type should be String? so that it fits the validator prototype!
Widget Field(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
);
}
String? checkFieldEmpty(String? fieldContent) { //<-- add String? as a return type
if(fieldContent.isEmpty) {
return 'Ce champ est obligatoire.';
}
return null;
}
A more appropriate way of doing this in Flutter
Remember, flutter is a declarative language. That is, you build your app by composing a widget tree. Here, you are using a function to return a Widget. That is breaking this rule. Instead, you should declare your own custom widget that implements the TextField Widget. Here's how:
1. Declare your custom widget
// Declare your CustomTextField as a Stateless/Stateful Widget
class MyCustomTextField extends StatelessWidget {
// Declare your custom vars, including your validator function
final String? changedValue;
final String? label;
final bool? isTextObscured;
final String? Function(String?)? validator;
const MyCustomTextField({
Key? key,
this.changedValue,
this.label,
this.isTextObscured,
this.validator,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: validator,
);
}
}
2. Use your custom widget
Now you can use this custom widget as a child of any other widget:
class ParentWidget extends StatelessWidget {
const ParentWidget({Key? key}) : super(key: key);
// This is your custom validator function and can leave
// anywhere ;)
Stirng? customValidtaor(String? fieldContent) => fieldContent.isEmpty? 'Ce champ est obligatoire.': null
#override
Widget build(BuildContext context) {
return MyCustomTextField(
label: 'Some label'
// declare the validator here...
// valiator: (fieldContent) => fieldContent.isEmpty? 'Ce champ est obligatoire.': null
// or use your custom validator function
validator: customValidator,
);
}
}
By doing this, you are respecting Flutter's best practices by using Widget composition ;)
Add String as a return type for checkFieldEmpty() method, see below
instead this:
checkFieldEmpty(String fieldContent) {}
use this:
String checkFieldEmpty(String fieldContent) {}
Full Code Example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(title: Text(appTitle)),
body: MyCustomForm(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
Widget textField(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
);
}
String checkFieldEmpty(String fieldContent) {
if (fieldContent.isEmpty) return 'Ce champ est obligatoire.';
return null;
}
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
textField('changedValue', 'Enter Password', false),
ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState.validate()) {
// If the form is valid,
// perform further actions here
}
},
child: Text('Submit'),
),
],
),
);
}
}
I would like to have a text field in a widget where you can search for something. If the string changes, the widget should be reloaded and the new data sent.
I've read many things about a callback function, unfortunately I don't quite understand it at flutter.
Below you will find the sample code:
Thanks for your help
class _HomepageState extends State<Homepage> {
String data;
String day;
...
TextField(
onChanged: (str){
setState(() {
data = str;
});
},
),
...
Nextpage(data: data, day: day,)
...
}
class Nextpage extends StatefulWidget {
final String data;
final String day;
Nextpage({this.data, this.day});
...
print(widget.data);
...
}
Below is an example where you have a TextField, and as its input changes, the text is shown on a different Text widget (you can copy it and paste it to DartPad to see how it works in action).
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String myText = '';
TextField buildTextField() {
return TextField(
decoration: InputDecoration(
labelText: 'Enter Text',
),
onChanged: (changedText) => updateText(changedText),
onEditingComplete: () => print("complete"),
);
}
void updateText(newText) {
setState(() {
myText = newText;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Example",
theme: ThemeData(primarySwatch: Colors.deepPurple),
home: Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildTextField(),
Text(myText),
]),
),
));
}
}
The callback is this part of the code:
onChanged: (changedText) => updateText(changedText)
and the callback function is updateText.
This is another example of a callback where the print function (the callback function) is called after the editing is complete.
onEditingComplete: () => print("complete")
I hope this helps.