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.
Related
I'm totally new to Flutter/Dart, I've done all the layouts for my application, and now it's time to make my application's API calls. I'm trying to manage the forms as cleanly as possible.
I created a class that manages TextFields data (values and errors), if my API returns an error I would like the screen to update without having to call setState(() {}), is this possible?
In addition, many of my application's screens use values that the user enters in real time, if that happened I would have to call the setState(() {}) methodmany times.
Any idea how to do this with the excess calls to the setState(() {}) method?
I created a test project for demo, these are my files:
File path: /main.dart
import 'package:flutter/material.dart';
import 'login_form_data.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final LoginFormData _loginFormData = LoginFormData();
void _submitLoginForm() {
// Validate and then make a call to the login api
// If the api returns any erros inject then in the LoginFormData class
_loginFormData.setError('email', 'Invalid e-mail');
setState(() {}); // Don't want to call setState
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test App'),
),
body: Padding(
padding: const EdgeInsets.all(30),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
decoration: InputDecoration(
errorText: _loginFormData.firstError('email'),
labelText: 'E-mail',
),
onChanged: (value) => _loginFormData.setValue('email', value),
),
TextField(
decoration: InputDecoration(
errorText: _loginFormData.firstError('password'),
labelText: 'Password',
),
obscureText: true,
onChanged: (value) =>
_loginFormData.setValue('password', value),
),
ElevatedButton(
onPressed: _submitLoginForm,
child: const Text('Login'),
)
],
),
),
),
);
}
}
File path: /login_form_data.dart
import 'form/form_data.dart';
import 'form/form_field.dart';
class LoginFormData extends FormData {
#override
Map<String, FormField> fields = {
'email': FormField(),
'password': FormField(),
'simple_account': FormField(
value: true,
),
};
LoginFormData();
}
File path: /form/form_data.dart
class FormData {
final Map<String, dynamic> fields = {};
dynamic getValue(
String key, {
String? defaultValue,
}) {
return fields[key]?.value ?? defaultValue;
}
void setValue(
String key,
String value,
) {
fields[key].value = value;
}
void setError(
String key,
String error,
) {
fields[key]?.errors.add(error);
}
dynamic firstError(
String key,
) {
return fields[key]?.errors.length > 0 ? fields[key]?.errors[0] : null;
}
FormData();
}
File path: /form/form_field.dart
class FormField {
dynamic value;
List errors = [];
FormField({
this.value,
});
}
You are essentially looking for a State Management solution.
There are multiple solutions (you can read about them here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/options)
State Management allows you to declare when you want your widgets to change state instead of having to imperatively call a setState method.
Flutter recommends Provider as a beginner solution, and you can find many tutorials online.
With that being said, let me show you how to achieve this result with a very basic solution: Change Notifier
Quoting flutter documentation :
” A class that can be extended or mixed in that provides a change
notification API using VoidCallback for notifications.”
We are going to make FormData a Change notifier, and them we are going to make your app listen to changes on the instance, and rebuild itself based on them.
Step 1:
Based on the code you posted, I can tell that you will interact with LoginFormData based on the methods setValue and setError from the parent class FormData. So we are going to make FormData inherit ChangeNotifer, and make a call to notifyListeners() on these two methods.
class FormData extends ChangeNotifier {
final Map<String, dynamic> fields = {};
dynamic getValue(
String key, {
String? defaultValue,
}) {
return fields[key]?.value ?? defaultValue;
}
void setValue(
String key,
String value,
) {
fields[key].value = value;
notifyListeners();
}
void setError(
String key,
String error,
) {
fields[key]?.errors.add(error);
notifyListeners();
}
dynamic firstError(
String key,
) {
return fields[key]?.errors.length > 0 ? fields[key]?.errors[0] : null;
}
FormData();
}
Now, every time you call either setValue or setError, the instance of FormData will notify the listeners.
Step2:
Now we have to setup a widget in your app to listen to these changes. Since your app is still small, it’s easy to find a place to put this listener. But as your app grows, you will see that it gets harder to do this, and that’s where packages like Provider come in handy.
We are going to wrap your Padding widget that is the first on the body of your scaffold, with a AnimatedBuilder. Despite of the misleading name, animated builder is not limited to animations. It is a widget that receives any listenable object as a parameter, and rebuilds itself every time it gets notified, passing down the updated version of the listenable.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final LoginFormData _loginFormData = LoginFormData();
void _submitLoginForm() {
// Validate and then make a call to the login api
// If the api returns any erros inject then in the LoginFormData class
_loginFormData.setError('email', 'Invalid e-mail');
//setState(() {}); No longer necessary
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test App'),
),
body: AnimatedBuilder(
animation: _loginFormData,
builder: (context, child) {
return Padding(
padding: const EdgeInsets.all(30),
child: Center(
child: Column(
//... The rest of your widgets
),
),
);
}
),
);
}
}
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 make small app this app have two pages. First page main page then second page. now in each pages I add function this function to can add new data in MySQL database. Now my problem the user is work in main page and when click button add in main page I need to make the method on the other page work as well in same time.
So how we can control function from other page? How I can call AddNewColor() in MainPage in button (add new data).
Note: This is just an example of what I am trying to do also to understand the idea and the problem. In my full application, I display the second page on the first page to display a set of images.
Main page code:
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MainPage> {
TextEditingController NameController = TextEditingController();
Future AddNewData() async {
final response = await http.post(Uri.parse("**********", ),
body: {
"Name": NameController.text,
}
);
if (response.statusCode == 200) {
} else {
throw Exception('Send Failed');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(children: [
TextFormField(
controller: NameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
ElevatedButton(
child: Text('add new data'),
onPressed: () {
AddNewData();
},
),
],)
),
);
}
}
Second Page code:
class secondpage extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<secondpage> {
TextEditingController ColorController = TextEditingController();
Future AddNewColor() async {
final response = await http.post(Uri.parse("**********", ),
body: {
"Color": ColorController.text,
}
);
if (response.statusCode == 200) {
} else {
throw Exception('Send Failed');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(children: [
TextFormField(
controller: ColorController,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
ElevatedButton(
child: Text('add new color'),
onPressed: () {
AddNewColor();
},
),
],)
),
);
}
}
Define AddNewColor() out of secondpage class:
Future AddNewColor(TextEditingController colorController) async {
final response = await http.post(Uri.parse("**********", ),
body: {
"Color": ColorController.text,
}
);
if (response.statusCode == 200) {
} else {
throw Exception('Send Failed');
}
}
class secondpage extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<secondpage> {
...
}
now you can call it every where you want. Note that use return way to pass the result data.
update:
try define ColorController here:
class _MyAppState extends State<MainPage> {
TextEditingController NameController = TextEditingController();
TextEditingController ColorController = TextEditingController();
Future AddNewData() async {
...
}
...
}
then change your second page to receive it like this:
class secondpage extends StatefulWidget {
final TextEditingController ColorController;
secondpage(this.ColorController);
#override
_MyAppState createState() => _MyAppState();
}
now you can use ColorController in both pages with AddNewColor.
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 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..