Related
I have a form with some inputs. I am using a GlobalKey<FormState> to handle submissions and validation and so on.
One of the fields is supposed to take a double input, so I validate that by trying to parse the input value to double like so :
return TextFormField(
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.number,
validator: (String value) {
double _parsedValue = double.tryParse(value);
if (_parsedValue == null) {
return "Please input a number";
}
},
onSaved: (String value) {
setState(() {
_price = double.parse(value);
});
},
);
Now that works as expected. However, if the user inputs for example 9,99 that would fail, because the parse expects 9.99 .
What I'm trying to do is, when the validator is called, I'd like to check the input string for any commas, and then if they are present, replace them with dots instead, and update the form value accordingly.
My question is - can we actually update the form state from within validators?
I think maybe what you need is a TextInputFormatter.
Here is a link to the docs https://docs.flutter.io/flutter/services/TextInputFormatter-class.html
There are pre-existing formatters you can use as a reference to convert comma's to dots.
I don't think you need to update the state in the validator. I would use only the save event to update the state. This way it gets very clear where the state is updated.
I believe nothing forbids you to update the state in the validate, but maybe it would get less organized. :)
Solution that do not exactly answer your question
I guess the best way to accomplish what you need would be using a TextInputFormatter with a WhitelistingTextInputFormatter, check it out:
Note the TextInputType.numberWithOptions(decimal: true) and that if the user pastes "-100,00" , it would become 100.0 - which for a price would be fine, but not for double values in general.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ValidatorState',
theme: ThemeData(primarySwatch: Colors.yellow),
home: MyFormPage(),
);
}
}
class MyFormPage extends StatefulWidget {
#override
_MyFormPageState createState() => _MyFormPageState();
}
class _MyFormPageState extends State<MyFormPage> {
final _formKey = GlobalKey<FormState>();
double _price;
void _save() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
Scaffold.of(_formKey.currentContext)
.showSnackBar(SnackBar(content: Text('New price defined! ($_price)')));
}
}
Widget _buildForm(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter(RegExp("[0-9.]"))
],
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (String value) {
double _parsedValue = double.tryParse(value);
if (_parsedValue == null) {
return "Please input a valid number";
}
if (_parsedValue == 0.0) {
return "Please input a valid price";
}
},
onSaved: (String value) {
setState(() {
_price = double.tryParse(value);
});
},
),
Text(""),
RaisedButton(
child: Text("Save"),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onPressed: _save,
),
Text(""),
TextFormField(
decoration: InputDecoration(labelText: 'Copy and Paste area'),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Validator State"),
),
body: Form(
key:_formKey,
child: _buildForm(context),
),
);
}
}
Solution that answers your question
However, that is not exactly what you described. You want to automatically replace , to .. I would avoid doing that, as 1,234.56 would translate to 1.234.56, which is invalid. If you only strip out the commas, you end up with 1234.56 which is valid.
If you really want to do as you said, you have to use a TextEditingController and a function to normalize the text data. I've made the example below, check it out - specially the _priceController and the _parsePrice.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ValidatorState',
theme: ThemeData(primarySwatch: Colors.yellow),
home: MyFormPage(),
);
}
}
class MyFormPage extends StatefulWidget {
#override
_MyFormPageState createState() => _MyFormPageState();
}
class _MyFormPageState extends State<MyFormPage> {
final _formKey = GlobalKey<FormState>();
TextEditingController _priceController;
double _price;
#override
void initState() {
super.initState();
_priceController = TextEditingController();
}
#override
void dispose() {
_priceController?.dispose();
super.dispose();
}
void _save() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
Scaffold.of(_formKey.currentContext)
.showSnackBar(SnackBar(content: Text('New price defined! ($_price)')));
}
}
double _parsePrice(String text) {
var buffer = new StringBuffer();
text.runes.forEach((int rune) {
// acceptable runes are . or 0123456789
if (rune == 46 || (rune >= 48 && rune <= 57)) buffer.writeCharCode(rune);
// if we find a , we replace with a .
if (rune == 44) buffer.writeCharCode(46);
});
return double.tryParse(buffer.toString());
}
Widget _buildForm(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
controller: _priceController,
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (String value) {
double _parsedValue = _parsePrice(value);
if (_parsedValue == null) {
return "Please input a valid number";
}
if (_parsedValue == 0.0) {
return "Please input a valid price";
}
},
onSaved: (String value) {
setState(() {
_price = _parsePrice(value);
_priceController.text = _price.toString();
});
},
),
Text(""),
RaisedButton(
child: Text("Save"),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onPressed: _save,
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Validator State"),
),
body: Form(
key:_formKey,
child: _buildForm(context),
),
);
}
}
hi did you get a fix for this?
I would rethink your strategy for this issue.
Maybe what you need is an observer function that is triggered when the user typing, which then looks at the comma and changes it to a dot.
TextFormField has a built in function,
onEditingCompleted and onFieldSubmitted which can run the function you have to make the check before the validate is run.
I have no problem populating the list from Sqflite database on DropdownButton. My only problem is updating the text once it's selected. It kept showing 'Airport' and I'm still learning to work with Object instead of String. I just couldn't figure that out.
Here's the code:
String selectedAirport;
AirportModel _currentAirport;
...
children: <Widget>[
FutureBuilder<List<AirportModel>>(
future: db.getAllAirports(),
builder: (BuildContext context, AsyncSnapshot<List<AirportModel>> snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return DropdownButton<AirportModel>(
items: snapshot.data
.map((airportItem) =>
DropdownMenuItem<AirportModel>(
value: airportItem,
child: Text(airportItem.airportName),
))
.toList(),
onChanged: (AirportModel value) {
setState(() {
_currentAirport = value;
selectedAirport = _currentAirport.airportName;
});
},
hint: Text("Airport"),
);
}),
DropdownButton has a property value. use it like value=_currentAirport
return DropdownButton<AirportModel>(
value:_currentAirport,
items: snapshot.data
.map((airportItem) =>
DropdownMenuItem<AirportModel>(
value: airportItem,
child: Text(airportItem.airportName),
))
.toList(),
onChanged: (AirportModel value) {
setState(() {
_currentAirport = value;
selectedAirport = _currentAirport.airportName;
});
},
hint: Text("Airport"),
);
Maybe items didn't reach yet or empty when value is set to DropdownButton. is _currentAirport initialized to some other value already?
Can you try like this? Also check if the items list are empty
items: snapshot.data == null ? null : _currentAirport
You can declare a Future and init in initState and in FutureBuilder use this future.
AirportModel _currentAirport;;
Future _future;
#override
void initState() {
_future = db.getAllAirports();
super.initState();
}
body: FutureBuilder<List<AirportModel>>(
future: _future,
You can use stream builder. Please check the example below.
class DropDownMenu extends StatefulWidget {
#override
_DropDownMenuState createState() => _DropDownMenuState();
}
class _DropDownMenuState extends State<DropDownMenu> {
var _currentSelectedValue;
final _dbHelper = DatabaseHelper.instance;
LoginPageManager _loginPageManager = new LoginPageManager();
final ValueNotifier<List<DropdownMenuItem<String>>> _dropDownMenuItems =
ValueNotifier<List<DropdownMenuItem<String>>>([]);
#override
void initState() {
_updateList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
width: 300,
height: 50,
margin: const EdgeInsets.only(top: 00.0),
child: ValueListenableBuilder(
builder: (BuildContext context, List<DropdownMenuItem<String>> list,
Widget child) {
return Container(
child: DropdownButton<String>(
hint: Text("Please Select a Server"),
value: _currentSelectedValue,
onChanged: (value) {
setState(() {
_currentSelectedValue = value;
});
},
items: list),
);
},
valueListenable: _dropDownMenuItems,
),
);
}
_updateList() async {
print("Update server has been called");
_dropDownMenuItems.value.clear();
List<Map<String, dynamic>> x = await _dbHelper.queryAllRows();
_dropDownMenuItems.value.add(_getAddServerButton());
x.forEach((element) {
_dropDownMenuItems.value.add(_getDropDownWidget(element));
});
}
DropdownMenuItem<String> _getDropDownWidget(Map<String, dynamic> map) {
int id = map['yxz'];
String text =
map['xyz'];
String value = map['zyx'];
return DropdownMenuItem<String>(
value: value,
child: Container(
width: 270,
child: Row(
children: [_getText(text), _getRemoveButton(id), _getEditButton(id)],
),
));
}
}
To make sure api data is not null:
child: _identity1 != null
? DropdownButtonFormField<dynamic>(
validator: (value) => value == null ? 'field required' : null
For Example This is the First Dropdownbutton
For Example This is the First Dropdown Sorry i dont have enough Reputation to post the images
Where the Tag will be Select A Region
and Another one will be showing which will be the cities where the cities will be
listed down there depends on the region selected above somewhat like that.
Each time you call setState the build method of your widget will be called and the visual tree gets reconstructed where needed. So, in the onChanged handler for your DropdownButton, save the selection in setState and conditionally add the second DropdownButton. Here's a working example (which may be a little rough around the edges :) ):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _selectedRegion;
String _selectedSecond;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Something before'),
DropdownButton<String>(
value: _selectedRegion,
items: ['Arizona', 'California']
.map((region) => DropdownMenuItem<String>(
child: Text(region), value: region))
.toList(),
onChanged: (newValue) {
setState(() {
_selectedRegion = newValue;
});
},
),
_addSecondDropdown(),
Text('Something after'),
],
),
),
);
}
Widget _addSecondDropdown() {
return _selectedRegion != null
? DropdownButton<String>(
value: _selectedSecond,
items: ['First', 'Second']
.map((region) => DropdownMenuItem<String>(
child: Text(region), value: region))
.toList(),
onChanged: (newValue) {
setState(() {
_selectedSecond = newValue;
});
})
: Container(); // Return an empty Container instead.
}
}
Luke Freeman has a great blog post about Managing visibility in Flutter if you need this in a more extensive/reusable way.
I have many screens, and I'm using the Navigator. I'd like to use "named routes", but I also need to pass non-string (such as images) to my next route.
I can't use pushNamed() because I can't pass non-string data to it.
How can I use a named route + send non-string data?
EDIT:
It is now possible to pass complex arguments to Navigator.pushNamed:
String id;
Navigator.pushNamed(context, '/users', arguments: id);
It can then be used within onGenerateRoute to customize route building with these arguments:
MaterialApp(
title: 'Flutter Hooks Gallery',
onGenerateRoute: (settings) {
final arguments = settings.arguments;
switch (settings.name) {
case '/users':
if (arguments is String) {
// the details page for one specific user
return UserDetails(arguments);
}
else {
// a route showing the list of all users
return UserList();
}
default:
return null;
}
},
);
You can use the parameter routes of your App for directly passing arguments.
Like this:
routes: {
HomePage.route: (_) => HomePage(),
DetailsPage.route: (context) =>
DetailsPage(ModalRoute.of(context).settings.arguments),
},
In this case, the complete example will look like the next:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
initialRoute: HomePage.route,
routes: {
HomePage.route: (_) => HomePage(),
DetailsPage.route: (context) =>
DetailsPage(ModalRoute.of(context).settings.arguments),
},
);
}
}
class HomePage extends StatelessWidget {
static const String route = '/';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, '/details',
arguments: ScreenArguments(
'My Details',
'Some Message',
));
},
),
);
}
}
class DetailsPage extends StatelessWidget {
static const String route = '/details';
final ScreenArguments arguments;
DetailsPage(this.arguments);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(arguments.title),
),
body: Center(
child: Text(arguments.message),
),
);
}
}
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
By Using Maps
While pushing the arguments u can push that in map form and can do the same while extracting them.
e.g.
While Pushing
Navigator.of(context).pushNamed(
'second',
arguments: {
'title':'This is a String',
or
'Fx': This could be any widget or Function
}
While Extracting the arguments in the target page
final routes=ModalRoute.of(context).settings.arguments as Map<String,String>;
return Scaffold(
appBar: AppBar(
title: Text(routes['title']),
),
body: Container(
child: Center(
child: RaisedButton(
child: Text("Back"),
onPressed: ()=>Navigator.of(context).pop(),
),
),
),
);
and choose your map accordingly accordingly
UPDATE: 3rd April, 2021
This answer is old and Flutter navigation has evolved considerably since then. This may not be the best way to handle navigation with current versions, please consider other answers. I will leave this here for historical purposes.
Using onGenerateRoute it is easy to pass complex arguments on route transition with Navigator.pushNamed or Navigator.pushReplacementNamed
A minimal setup to show the concept would be
main.dart
import 'package:flutter/material.dart';
import 'package:navigator/routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Navigation Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => makeRoute(
context: context,
routeName: settings.name,
arguments: settings.arguments,
),
maintainState: true,
fullscreenDialog: false,
);
},
);
}
}
routes.dart
In the _buildRoute method we check the route name and cast arguments to a required type.
A draw back is that the type has to be defined before hand if required argument is not a simple type.
import 'package:flutter/material.dart';
import 'package:navigator/list.dart';
import 'package:navigator/details.dart';
Widget makeRoute(
{#required BuildContext context,
#required String routeName,
Object arguments}) {
final Widget child =
_buildRoute(context: context, routeName: routeName, arguments: arguments);
return child;
}
Widget _buildRoute({
#required BuildContext context,
#required String routeName,
Object arguments,
}) {
switch (routeName) {
case '/':
return ArticleList();
case '/ArticleView':
Article article = arguments as Article;
return ArticleView(article: article);
default:
throw 'Route $routeName is not defined';
}
}
Views
list.dart
Construct the route argument using a defined type, Article in our case.
import 'package:flutter/material.dart';
import 'package:navigator/details.dart' show Article;
class ArticleList extends StatefulWidget {
#override
_ArticleListState createState() => _ArticleListState();
}
class _ArticleListState extends State<ArticleList> {
List<Article> articles = [
Article(
id: 1,
title: 'Article 1',
author_name: 'Nilotpal',
summary: 'Article 1 summary'),
Article(
id: 2,
title: 'Article 2',
author_name: 'Mike',
summary: 'Article 2 summary'),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Articles'),
),
body: Center(
child: Column(
children: <Widget>[
ListTile(
title: Text('${articles[0].title}'),
subtitle: Text('by ${articles[0].author_name}'),
onTap: () {
Navigator.of(context)
.pushNamed('/ArticleView', arguments: articles[0]);
},
),
ListTile(
title: Text('${articles[1].title}'),
subtitle: Text('by ${articles[1].author_name}'),
onTap: () {
Navigator.of(context)
.pushNamed('/ArticleView', arguments: articles[1]);
},
),
],
),
),
);
}
}
details.dart
Define a type for the arguments
import 'package:flutter/material.dart';
class Article {
final int id;
final String author_name;
final String title;
final String summary;
Article(
{#required this.id,
#required this.author_name,
#required this.title,
#required this.summary});
}
class ArticleView extends StatelessWidget {
final Article _article;
ArticleView({#required Article article}) : _article = article;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${_article.title}'),
),
body: SafeArea(
top: true,
child: Center(
child: Column(
children: <Widget>[
Text('${_article.author_name}'),
Text('${_article.summary}'),
],
),
),
),
);
}
}
The Flutter Cookbook shows how to navigate to a new page and pass non-string data to it.
Passing data to next page
I started with Navigator.pushedNamed() because it was simple and I didn't have any data to pass. When my needs changed and I wanted to pass data, I switched to Navigator.push().
Example:
var nextPageData = {foo:'bar'};
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
MyNextPage(myData: nextPageData))
);
I am capturing images with camera then passing them through to a confirmation page like so:
ImagePicker.pickImage(source: source).then((File file) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MediaCaptured(file: file),
));
});
You could easily do the same with any type of file or non-string data.
var foo = "non-string data";
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MediaCaptured(foo: foo),
));
Call the next page in the route by it's class name, as above.
Just make sure your new page accepts this in it's constructor.
// Stateful Widget
class MediaCaptured extends StatefulWidget {
MediaCaptured({ Key key, #required this.foo,}) : super(key: key);
final var foo;
}
// StatelessWidget
class MediaCaptured extends StatelessWidget {
MediaCaptured(this.foo);
var foo;
}
For the outcome of this problem, I developed the package
link: https://pub.dartlang.org/packages/navigate
That provide to much your expect and easy to use
Navigate.navigate(context,
"home",
transactionType:TransactionType.fromLeft , // optional
replaceRoute: ReplaceRoute.thisOne, //optional
arg: {"transactionType":TransactionType.fromLeft,"replaceRoute":ReplaceRoute.thisOne} //optional
);
From First StateFul class :
Navigator.of(context).pushNamed('/pending_order',arguments: {"staff" : staffObj});
To Second StateFul class :
class PendingOrders extends StatefulWidget {
#override
_PendingOrdersState createState() => _PendingOrdersState();
}
class _PendingOrdersState extends State<PendingOrders> {
StaffModel staffModelObj;
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context).settings.arguments as Map<String, dynamic>;
if (routes != null) {
staffModelObj = routes["staff"];
}
return Scaffold(...);}}
I wanted to use a named route navigator that has values as below
Navigator.pushNamed(context, '/increaseBalanceAccountPage',
arguments: {'accountBalanceViewModel': result},);
so I should define that route in materialApp widget in the start of app but I should give parameters in the instance so I solve my problem with some modification of #YuriyLuchaninov code Like below:
MaterialApp(
initialRoute: "/",
routes: {
'/': (context) => SplashScreenPage(),
"/increaseBalanceAccountPage":
(context) =>
UserAccountBalancePage(accountBalanceViewModel: Map<String,Object>
.from(ModalRoute.of(context)!.settings.arguments as Map).values.first as
AccountBalanceViewModel)
},
.....
Consider this trivial example from flutter. You have a class created as follows
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
Now we will pass an object of this class as an argument as follows
Navigator.pushNamed(
context,
ExtractArgumentsScreen.routeName,
arguments: ScreenArguments(
'Extract Arguments Screen',
'This message is extracted in the build method.',
),
);
And then you can extract the arguments as follows
final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
And that is all. Hope this helps Source: passing arguments to a named route
We can pass any type of arguments when declaring routes as constructor arguments as below,
For example to send a list of Strings,
List<String> titles = [];
void main() => runApp(
new MaterialApp(
home: new FirstPage(),
routes: <String, WidgetBuilder>{
"/SecondPage": (BuildContext context) => new SecondPage(titles),
},
),
);
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
child: new RaisedButton(onPressed: () {
Navigator.of(context).pushNamed('/SecondPage');
}),
);
}
}
class SecondPage extends StatelessWidget {
final List<String> titles;
SecondPage(this.titles);
#override
Widget build(BuildContext context) {
return new ListView.builder(
itemBuilder: (context, index) {
return new ListTile(
title: new Text(titles[index]),
);
},
);
}
}
How to get selectedIndex of dropdown in flutter,
In dropdownbutton there is no property to get selected index, if there how to get the selected index and my code look like this:
new DropdownButton( hint:new Text("Select a users"),value: selectedUser,
onChanged: (String newValue) {
setState(() {
selectedUser = newValue;
});
},
items: userInfoToMap.map((ListOfUsers value) {
return new DropdownMenuItem<String>(
value: value.name,
child:new Text(value.name,style: new TextStyle(color: Colors.black))
);
})
.toList(),
),
),),
You should probably use a custom model object (e.g. User) as the type for DropdownButton.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class User {
const User(this.name);
final String name;
}
class MyApp extends StatefulWidget {
State createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
User selectedUser;
List<User> users = <User>[User('Foo'), User('Bar')];
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Center(
child: new DropdownButton<User>(
hint: new Text("Select a user"),
value: selectedUser,
onChanged: (User newValue) {
setState(() {
selectedUser = newValue;
});
},
items: users.map((User user) {
return new DropdownMenuItem<User>(
value: user,
child: new Text(
user.name,
style: new TextStyle(color: Colors.black),
),
);
}).toList(),
),
),
),
);
}
}
Similar to Collin Jackson's answer, you can simply use a List of Strings and check the indexOf to set the value, which might be preferable in some situations rather than making a User class.
If you want to set an initial value set _user to an integer value when defined.
int _user;
...
var users = <String>[
'Bob',
'Allie',
'Jason',
];
return new DropdownButton<String>(
hint: new Text('Pickup on every'),
value: _user == null ? null : users[_user],
items: users.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (value) {
setState(() {
_user = users.indexOf(value);
});
},
);
This gives you selected index of your dropdown
userInfoToMap.indexOf(selectedUser);