How the onChange TextField pass the data over into button onPressed property - flutter

I'm trying to pass data from TextField with onChange property to onPressed button.
If I type my string as below:
String newTextTitle;
then I get error on print(newTextTitle);:
The non-nullable local variable 'newTextTitle' must be assigned before it can be used.
Try giving it an initializer expression, or ensure that it's assigned on every execution path.
So I change it to
String? newTextTitle;
then the error won't appear again. But The data won't pass from TextField to my button, actually passing null.
And if I assigned some string then it is printing always what I assigned regardless of any change in the TextField.
My TextField code:
TextField(
autofocus: true,
textAlign: TextAlign.center,
onChanged: (newValue) {
newTextTitle = newValue;
},
),
My button code:
TextButton(
onPressed: () {
print('Passing Test $newTextTitle');
},
),
my output consol:
I/flutter (23788): Passing Test null
This code is worked so fine in older flutter.
But now I used Flutter 2.5.2 and there is somthing has been changed.

Using a TextEditingController is the Flutter recommended way of doing what your trying to do. See (https://flutter.dev/docs/cookbook/forms/text-field-changes)
// Setup your controller
final TextEditingController _controller = TextEditingController();
// Add controller to your TextField
TextField(
controller: _controller,
),
// Get the text via controller.
TextButton(
onPressed: () {
print(_controller.text);
},
child: const Text('click')
)

That's weird works completely fine for me. Maybe match the code if you wish I can leave a sample here. Make sure you are not assigning
For the onChange function make sure you are using ``TextFormField``` instead. Although it shouldn't matter in the first place something you can try as well.
It seems like that you might be assigning string within build context and within the scope of the current text field, that's mostly the reason. A sample would be a lot helpful in that case so I can re-edit my answer for your use case.
newTextTitle in the scope of the build function.
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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
String? newTextTitle;
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(children: [
TextFormField(
onChanged: (newValue) {
newTextTitle = newValue;
},
),
TextButton(
onPressed: () {
print(newTextTitle);
},
child: const Text('Buttom'))
]),
));
}
}
Compare the code and I am on Flutter 2.5.3
String? newTextTitle; //past it here
Buildwidget(){
String? newTextTitle; //remove from here
}

Related

Flutter/Dart - Update state from an external class

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
),
),
);
}
),
);
}
}

TextButton is automatically invoked when the dropDown menu item is changed

Here goes my code ,
I am using the TextButton to update the order ,But after every change in the dropdown item, onPress is automatically invoked and the function updateOrder is automatically invoked
import 'package:admin/constants/Constants.dart';
import 'package:admin/model/order_model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class DetailsScreen extends StatefulWidget {
const DetailsScreen({Key? key}) : super(key: key);
#override
State<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
List<String> _dropDownQuantities = [Pending, Confirmed, Rejected, Success];
late OrderModel order;
late String selectedStatus = order.status;
#override
Widget build(BuildContext context) {
order = ModalRoute.of(context)?.settings.arguments as OrderModel;
return Scaffold(
appBar: AppBar(
actions: [],
title: Text("Order Details"),
),
body: Column(children: [
Text(order.id),
DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: selectedStatus,
items: _dropDownQuantities
.map((e) => DropdownMenuItem<String>(
child: Text(e),
value: e,
))
.toList(),
onChanged: (value) {
setState(() {
selectedStatus = value;
});
},
)),
TextButton(onPressed: updateOrder(order, selectedStatus), child: Text("Confirm")),
]));
}
}
updateOrder(OrderModel order, String selected) {
print("I am executed");
}
So whenever i change the dropDown menu,
I am executed is printed in the console.
Edit:
But when i used the container with InkWell it was working fine. Why not working with TextButton ?
You are directly calling the method on build, you can create an inline anonymous function to handle this.
TextButton(
onPressed: ()=> updateOrder(order, selectedStatus),
child: Text("Confirm")),
onPressed Called when the button is tapped or otherwise activated.
While we use onPressed:method() call on every build, on dropDown onChanged we use setState, and it rebuilds the UI and onPressed:method() call again.
What we need here is to pass a function(VoidCallback) that will trigger while we tap on the button. We provide it like,
onPressed:(){
myMethod();
}
More about TextButton.

Why do my variables are successful changed in a StatelessWidget, even tho I get a warning

This is the warning:
This class (or a class which this class inherits from) is marked as '#immutable', but one or more of its instance fields are not final: MyWidget.myVar
The console tells me that the variable must be final. I suspect that I should change my widget to a stateful if I want to change variables, but to me it doesn't makes sense, as the code works as intended. When I change my variable I don't want to change anything on the screen, I just want to use it later.
What I'm doing is wrong? If not, how can I disable this warning?
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: MyWidget(),
),
);
}
class MyWidget extends StatelessWidget {
String myVar;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
MaterialButton(
child: Text('Click me do change variable'),
onPressed: () {
myVar = 'Clicked!';
},
),
MaterialButton(
child: Text('Click me to print the variable'),
onPressed: () {
print(myVar);
},
),
],
),
);
}
}
Your logic is correct and it doesn't really matter since you are not outputting anything on screen. However the best practise is to change it to a Stateful Widget.
It doesn't really affect it in a negative way.
You are getting the warning because all fields in a class extending StatelessWidget should be final.
Fix the warning by adding the final keyword before the type declaration like below:
final String myVar;
From the documentations.
StatelessWidget class. A widget that does not require mutable state.
https://docs.flutter.io/flutter/widgets/StatelessWidget-class.html
I hope this answers your question
The point of changing variable's value in a stateful widget is that you can call
setState(() {
myVar = 'clicked';
});
Which would rebuild the UI, changing a Text widget's content.
Try adding a Text(myVar) to your column, in a stateless widget it wouldn't change on a press of a button. But in a stateful widget it will change.
If you need to change the state of a variable in a Widget, you need to use a StetefullWidget.
class MyWidget extends StatefulWidget {
final String myInitialVar;
const MyWidget({Key key, this.myInitialVar}) : super(key: key);
#override
State<StatefulWidget> createState() => MyWidgetState(myInitialVar);
}
class MyWidgetState extends State<MyWidget> {
String myVar;
MyWidgetState(this.myVar);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
MaterialButton(
child: Text('Click me do change variable'),
onPressed: () {
setState(() {
myVar = 'Clicked!';
});
},
),
MaterialButton(
child: Text('Click me to print the variable'),
onPressed: () {
print(myVar);
},
),
],
),
);
}
}

Flutter formfield abnormality

I'm getting an abnormal effect in Flutter and I'm not sure if it's an error in my code or with flutter.
I'm trying to create a FormField object that is a checkbox that can have three values: empty, checked positive, or checked negative. Here is my code so far:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Checkbox Test',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Form(child: MyHomePage(title: 'Checkbox Test')),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _savedList = [];
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new CheckboxFormField(
title: "Option 1",
saved: _savedList,
themeData: themeData,
),
new CheckboxFormField(
title: "Option 2",
saved: _savedList,
themeData: themeData,
),
new CheckboxFormField(
title: "Option 3",
saved: _savedList,
themeData: themeData,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: () {Form.of(context).reset();},
tooltip: 'Reset',
child: new Icon(Icons.refresh),
),
);
}
}
class CheckboxFormField extends FormField<dynamic> {
CheckboxFormField({
Key key,
bool checkState,
bool initialValue = false,
Color activeColor,
String title,
List<String> saved,
ThemeData themeData,
}) : super(
key: key,
initialValue: initialValue,
builder: (FormFieldState<dynamic> field) {
void onChange() {
if (field.value == false) {
activeColor = Colors.green;
print(activeColor);
checkState = true;
field.didChange(true);
saved.add(title);
print(saved);
} else if (field.value == true) {
activeColor = Colors.red;
print(activeColor);
checkState = null;
field.didChange(null);
saved.remove(title);
saved.add("Not " + title);
print(saved);
} else {
activeColor = themeData.textTheme.subhead.color;
checkState = false;
field.didChange(false);
saved.remove("Not " + title);
print(saved);
}
}
return Checkbox(
tristate: true,
value: field.value,
activeColor: activeColor,
onChanged: (value) => onChange());
});
}
Now this works fine when the app is first started, and does exactly what's expected, with each click cycling through the appropriate color and adding the appropriate value to the list.
The problem arises if the Form is reset: function-wise it remains working normally, but the color changing stops working. The checkbox reverts to the theme's base togglableActiveColor, so despite my function's rotation still being preserved with empty box -> positive check -> negative check -> repeat, the checkbox color becomes togglableActiveColor when it is set to true or null, and unselectedWidgetColor when it is false.
I know the code is still working as the checkbox changes appropriately, and the saved list continues to be appended and removed from appropriately, just the color stops behaving. Is this a glitch in the engine or am I missing something in my code?
Thanks
Bry
EDIT: Continuing to look into it further it seems that on the rebuild by Form.of(context).reset the activeColor property somehow gets lost, while everything else is preserved. Maybe this is by design, any ideas?

How do I apply Material Design-compliant padding to my Flutter Scaffold?

I have written a Flutter application that makes use of package:flutter/material.dart. Running the app on the iOS Simulator looks as follows. As you can see, there is no padding between components in one row, and the components reach to top, bottom, left and right without padding/margin/border. My question is:
What is the recommended way to apply Material-compliant padding, for example for the label-component-gap between Convert to and the dropdown button. Would I pack my components in a Container and apply padding there?
Thank you very much in advance.
This is the application code:
import 'package:flutter/material.dart';
import 'converter.dart';
import 'model.dart';
const _appName = 'Temperature Converter';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: _appName,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: _appName),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Model model = new Model();
var _currentInput = const InputValue();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(config.title),
),
body: new Column(children: <Widget>[
new Row(children: <Widget>[
new Expanded(
child: new Input(
hintText: 'Temperature',
value: _currentInput,
onChanged: (input) => _currentInput = input)),
new DropdownButton(
items: createUnit(),
onChanged: (temperatureUnit newValue) {
setState(() {
model.inUnit = newValue;
});
},
value: model.inUnit)
]),
new Row(children: <Widget>[
new Text("Convert to"),
new DropdownButton(
items: createUnit(),
onChanged: (temperatureUnit newValue) {
setState(() {
model.outUnit = newValue;
});
},
value: model.outUnit),
]),
new FlatButton(
child: new Text('Calculate'),
onPressed: () {
setState(() {
double inTemp = stringToDouble(_currentInput.text);
model.inTemperature = inTemp;
model.calculateOutTemperature();
});
}),
new Text(model.outTemperatureAsString)
]), // This trailing comma tells the Dart formatter to use
// a style that looks nicer for build methods.
);
}
List<DropdownMenuItem> createUnit() {
var l = new List<DropdownMenuItem<temperatureUnit>>();
// degrees Celsius
l.add(new DropdownMenuItem<temperatureUnit>(
value: temperatureUnit.degreesCelsius,
child: new Text(unitDegreesCelsius)));
// degrees Fahrenheit
l.add(new DropdownMenuItem<temperatureUnit>(
value: temperatureUnit.degreesFahrenheit,
child: new Text(unitDegreesFahrenheit)));
// Kelvin
l.add(new DropdownMenuItem<temperatureUnit>(
value: temperatureUnit.kelvin, child: new Text(unitKelvin)));
return l;
}
}
The question is by no means implying that Flutter is missing something. I merely want to do it right. ;-)
Generally I would recommend using a Block as the child of the Scaffold. It has a padding value that you can set.
You can also just use a Padding widget.
I only know ButtonTheme that have a padding value
ButtonTheme.of(context).padding
So I think the best solution for now is to have constant value and use a Container to apply your padding.
Maybe I am wrong but I don't know any Widget that automatically apply any Material padding.