I am trying to use a DropdownButton
within a Form
to update a database record but unless I call didChange
inside the onChanged
callback the value written to the database is null
instead of the value selected from the DropdownMenu
.
From the documentation, the purpose of didChange
is to trigger onChanged
, so it doesn't seem to make any sense to have to call it from INSIDE onChanged
. But if I don't, the code doesn't work.
See:
import 'package:flutter/material.dart';
void main() => runApp(MinimalReproducibleExample());
class MinimalReproducibleExample extends StatefulWidget {
@override
_MinimalReproducibleExampleState createState() => _MinimalReproducibleExampleState();
}
class _MinimalReproducibleExampleState extends State<MinimalReproducibleExample> {
Enquiry _newEnquiry = Enquiry();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String _selectedAgent = '';
String _selectedManager = '';
@override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: Form(
key: _formKey,
child: Column(
children: [
FormField(
builder: (state) {
return DropdownButton(
items: ['', 'A'].map((val) => DropdownMenuItem<String>(child: Text(val), value: val)).toList(),
value: _selectedAgent,
onChanged: (val) => setState(() {
_selectedAgent = val;
// state.didChange(val);
}),
);
},
onSaved: (val) => _newEnquiry.agent = val,
),
FormField(
builder: (state) {
return DropdownButton(
items: ['', '1'].map((val) => DropdownMenuItem<String>(child: Text(val), value: val)).toList(),
value: _selectedManager,
onChanged: (val) => setState(() {
_selectedManager = val;
state.didChange(val);
}),
);
},
onSaved: (val) => _newEnquiry.manager = val,
),
RaisedButton(
onPressed: () {
_formKey.currentState.save();
print(_newEnquiry.agent);
print(_newEnquiry.manager);
},
),
],
),
),
),
);
}
}
class Enquiry {
String agent;
String manager;
Enquiry({this.agent, this.manager});
}
Running the code above (in Chrome, in case the device makes a difference) results in: "null
1" being printed. But when you un-comment the commented line the result is "A 1".
Why is it necessary? Or what am I doing wrong?
You can copy paste run full code below
Reason is FormField builder
has it's own State
, outside onSaved
can only get value after you call state.didChange
For detail, you can reference Building the CounterFormField
part of https://medium.com/saugo360/creating-custom-form-fields-in-flutter-85a8f46c2f41
From document :
the most important of them is the didChange
method, which we should call with the new value whenever the value of the field changes. This method updates the field’s value, informs the parent form of the change, and rebuilds the widget.
In your case, you can use DropdownButtonFormField
instead
code snippet
DropdownButtonFormField<int>(
value: _ratingController,
items: [1, 2, 3, 4, 5]
.map((label) => DropdownMenuItem(
child: Text(label.toString()),
value: label,
))
.toList(),
hint: Text('Rating'),
onChanged: (value) {
setState(() {
_ratingController = value;
});
},
onSaved: (val) {
print('onSaved drowndownformfield $val');
},
),
working demo
output
I/flutter (26553): validating null
I/flutter (26553): onSaved A null
I/flutter (26553): onSaved 1 1
I/flutter (26553): onSaved drowndownformfield 2
I/flutter (26553): null
I/flutter (26553): 1
full code
import 'package:flutter/material.dart';
class MinimalReproducibleExample extends StatefulWidget {
@override
_MinimalReproducibleExampleState createState() =>
_MinimalReproducibleExampleState();
}
class _MinimalReproducibleExampleState
extends State<MinimalReproducibleExample> {
Enquiry _newEnquiry = Enquiry();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String _selectedAgent = '';
String _selectedManager = '';
int _ratingController;
@override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: Form(
key: _formKey,
child: Column(
children: [
FormField(
builder: (state) {
return DropdownButton(
items: ['', 'A']
.map((val) => DropdownMenuItem<String>(
child: Text(val), value: val))
.toList(),
value: _selectedAgent,
onChanged: (val) => setState(() {
_selectedAgent = val;
// state.didChange(val);
}));
},
validator: (val) {
print('validating $val');
return null;
},
onSaved: (val) {
print('onSaved A $val');
_newEnquiry.agent = val;
},
),
FormField(
builder: (state) {
return DropdownButton(
items: ['', '1']
.map((val) => DropdownMenuItem<String>(
child: Text(val), value: val))
.toList(),
value: _selectedManager,
onChanged: (val) => setState(() {
_selectedManager = val;
state.didChange(val);
}),
);
},
onSaved: (val) {
print('onSaved 1 $val');
_newEnquiry.manager = val;
},
),
DropdownButtonFormField<int>(
value: _ratingController,
items: [1, 2, 3, 4, 5]
.map((label) => DropdownMenuItem(
child: Text(label.toString()),
value: label,
))
.toList(),
hint: Text('Rating'),
onChanged: (value) {
setState(() {
_ratingController = value;
});
},
onSaved: (val) {
print('onSaved drowndownformfield $val');
},
),
RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print(_newEnquiry.agent);
print(_newEnquiry.manager);
}
},
),
],
),
),
),
);
}
}
class Enquiry {
String agent;
String manager;
Enquiry({this.agent, this.manager});
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: "test",
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: MinimalReproducibleExample()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With