I am trying to store data in a class to store this information and use them later, but I am getting this error message and can't find out what I have to change to make it work. Here is a simplified version of my code where I get the same error.
class LUI {
String lui;
LUI({
this.lui,
});
}
class Test extends StatefulWidget {
final LUI data;
Test({
Key key,
this.data,
});
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
TextEditingController _controller = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Container(
child: TextField(
controller: _controller,
keyboardType: TextInputType.number,
))),
TextButton(
child: Text('add information'),
onPressed: () {
_sendresult(context);
print(widget.data.lui);
},
),
],
),
),
);
}
void _sendresult(BuildContext context) {
final result = _controller.text;
print(result);
setState(() {
widget.data.lui = result;
});
}
}```
The problem is that, the Test class expects a value to be passed for the data argument. However as you are not passing any value to that while creating the instance, which makes the field to remain null.
Later when you access the same field in the print(widget.data.lui); it results in a null pointer exception. As you already ahve figured, the soultion is to pass a value to data.
However, you can write your code in a better way so that you can catch this error in the compile time rather than waiting till the runtime.
Approach 1: (With Null-Safety enabled)
Use the keyword required to specify the filed is a must e.g.
Test({
Key key,
required this.data,
});
Now, with this, creating an instance like Test() with arguments input will throw compilation errors compalining about the missing input argument.
Approach 2: (Without Null-Safety enabled)
Use the annotation #required to specify the filed is a required e.g.
Test({
Key key,
#required this.data,
});
In this case, failing to pass an argument will throw a compile time warning (yellow line highlighter) which unlike the above case will inform you but will not block your execution.
I had to send a value for the data parameter from where I create the instance of the Test class. Thanks, Sisir for your answer.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Basis',
theme: ThemeData(
primaryColor: Colors.red,
textTheme: TextTheme(
headline1: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
home: Test(data: new LUI()));
}
}
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
),
),
);
}
),
);
}
}
The state here is maintained in a list of instances of Products called _shoppingCart
(The following code is an example from https://docs.flutter.dev/development/ui/widgets-intro#keys). The state is being mapped to widgets and every time a change is made to the list of products, all the widgets part of the list, regardless of being changed, still rebuild. Is this how it is supposed to be? or is there a better way?
import 'package:flutter/material.dart';
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = Function(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({
required this.product,
required this.inCart,
required this.onCartChanged,
}) : super(key: ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
// The theme depends on the BuildContext because different
// parts of the tree can have different themes.
// The BuildContext indicates where the build is
// taking place and therefore which theme to use.
return inCart //
? Colors.black54
: Theme.of(context).primaryColor;
}
TextStyle? _getTextStyle(BuildContext context) {
if (!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
#override
Widget build(BuildContext context) {
print("rebuilding ${product.name}");
return ListTile(
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: _getColor(context),
child: Text(product.name[0]),
),
title: Text(
product.name,
style: _getTextStyle(context),
),
);
}
}
class ShoppingList extends StatefulWidget {
const ShoppingList({required this.products, super.key});
final List<Product> products;
// The framework calls createState the first time
// a widget appears at a given location in the tree.
// If the parent rebuilds and uses the same type of
// widget (with the same key), the framework re-uses
// the State object instead of creating a new State object.
#override
State<ShoppingList> createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
final _shoppingCart = <Product>{};
void _handleCartChanged(Product product, bool inCart) {
setState(() {
// When a user changes what's in the cart, you need
// to change _shoppingCart inside a setState call to
// trigger a rebuild.
// The framework then calls build, below,
// which updates the visual appearance of the app.
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping List'),
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((product) {
return ShoppingListItem(
//key: ObjectKey(product),
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
void main() {
runApp(const MaterialApp(
title: 'Shopping App',
home: ShoppingList(
products: [
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
));
}
I think you don't need to worry about this, keep using const or value keys where you think that these widgets will not be changed,
Otherwise let Flutter framework handle this,
Flutter framework is smart enough, during setState it will only update the element tree, will not create/paint it from start,
So only updated elements will be repainted rest will be there.
I want to reuse different types of fields in different forms and I have created a separate Widget that returns TextFormField.
Logically, different types of fields have their own validations and other properties, so I have started looking into inheritance and so on to avoid rewriting same chunks of code.
From what I have learnt, Flutter does not encourage inheritance of widgets, so my question is on the best practices of reusing code for various form fields in flutter to remain readability and keep the code clean.
Any tips?
In my experience, I rarely had the need to use other widgets than the original form fields provided by flutter. What I found useful to reuse are validation functions for each fields, since they often have common needs in term of validation.
These are just two basic samples. I pass them to the validator argument of the form field whenever it's needed.
String? validatorForMissingFields(String? input) {
if (input == null || input.isEmpty || input.trim().isEmpty) {
return "Mandatory field";
}
return null;
}
String? validatorForMissingFieldsAndLength(String? input, int length) {
if (input == null || input.isEmpty || input.trim().isEmpty) {
return "Mandatory field";
}
if (input.length != length) {
return 'Not long enough';
}
return null;
}
In any case, instead of extending a basic widget, I prefer to create a new one containing the basic widget with some fixed properties, and others that can be customized. This example does not involve form fields, but I think it can better explain my point.
///if text is not null, icon is ignored
class RectButton extends StatelessWidget {
final Function()? onPressed;
final String? text;
final IconData? icon;
final Color color;
const RectButton({this.text, this.icon, required this.onPressed, Key? key, this.color = mainLightColor}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: OutlinedButton(
style: ButtonStyle(
side: MaterialStateProperty.all(BorderSide(color: color)),
overlayColor: MaterialStateColor.resolveWith((states) => color.withOpacity(0.5)),
backgroundColor: MaterialStateColor.resolveWith((states) => color.withOpacity(0.3)),
),
onPressed: onPressed,
child: text != null
? Text(
text!,
style: TextStyle(fontWeight: FontWeight.bold, color: color),
)
: Icon(
icon,
color: color,
)),
);
}
}
In order to maintain the same look&feel in all the app, I created a custom button with some 'invisible' widgets above it that allowed me to set some properties without extending a basic widget. The properties I needed to be customized are passed to the constructor.
You can create a class to store only the important things like a label or a controller and then use a wrap widget and a for loop to generate the widgets.
Here's an example:
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: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<TextFieldData> _allFieldData = [
TextFieldData(
label: 'field 1',
validator: numberOnlyValidator,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
TextFieldData(
label: 'field 2',
validator: canBeEmptyValidator,
),
TextFieldData(
label: 'field 3',
validator: numberOnlyValidator,
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Form(
child: Wrap(
runSpacing: 16,
spacing: 16,
children: [
for (var fieldData in _allFieldData)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 250),
child: TextFormField(
decoration: InputDecoration(label: Text(fieldData.label)),
controller: fieldData.controller,
autovalidateMode: fieldData.autovalidateMode,
validator: fieldData.validator,
),
)
],
),
),
),
),
);
}
}
const String numbersOnlyError = 'Only numbers';
const String requiredFieldError = 'Required field';
RegExp numbersOnlyRegexp = RegExp(r'^[0-9]\d*(,\d+)?$');
String? numberOnlyValidator(String? value) {
if (value == null || value.isEmpty) {
return requiredFieldError;
} else if (!numbersOnlyRegexp.hasMatch(value)) {
return numbersOnlyError;
}
return null;
}
String? canBeEmptyValidator(String? value) {
return null;
}
class TextFieldData {
final String label;
final String? Function(String?)? validator;
final AutovalidateMode autovalidateMode;
TextEditingController controller = TextEditingController();
TextFieldData({
required this.label,
required this.validator,
this.autovalidateMode = AutovalidateMode.disabled,
});
}
And then you can do whatever you want using the .controller of each item inside _allFieldData
Note: I put everything in the same file for simplicity but you would normally have the class and the validators in separate files.
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
}
I've been following some of the beginner flutter tutorials on their website and was doing this tutorial for basic interactivity, specifically the part where a parent widget is used to manage the state of a child widget. There is a ParentWidget and _ParentWidgetState class the code for which is as follows:
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
TapboxB is a class which is a child of ParentWidget, the code for which is as follows:
class TapboxB extends StatelessWidget {
TapboxB({this.active: false, #required this.onChanged});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Column(
//aligns column in the centre vertically
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
//sets text depending on _active boolean
active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 20.0, color: Colors.white),
),
Text(
'Tapbox B',
style: TextStyle(fontSize: 14.0, color: Colors.white),
),
],
),
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
//sets colour depending on _active boolean
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
The _handleTap method is called when the widget is clicked, which calls the onChanged callback, which toggles the active variable. In the definition for onChanged the type is ValueChanged<bool> which is documented as a "signature for callbacks that report that an underlying value has changed." If I change this however to ValueSetter<bool> the app works in the exact same way and nothing seems to change. So my question is what is the difference between the two of these? Is one better in this particular scenario?
I searched the documentation for you, using just flutter ValueChanged ValueSetter, and quickly found this:
void ValueSetter (T Value)
Signature for callbacks that report that a value has been set.
This is the same signature as ValueChanged, but is used when the callback is called even if the underlying value has not changed. For example, service extensions use this callback because they call the callback whenever the extension is called with a value, regardless of whether the given value is new or not.
typedef ValueSetter<T> = void Function(T value);
So they're just typedefs to the same underlying type, but one has a different semantic meaning, presumably used as self-documenting code despite the same underlying signature.
If you don't need the latter meaning of ValueSetter, then use ValueChanged, as the API already said.