How to add text on dropdownmenu? - flutter

I'm making an app using Flutter and I want to add text for my DropDownMenu like Select car make or something like that.
I could not find a tutorial so was hoping someone could help.
Here is my code:
new FormField(builder: (FormFieldState state) {
return InputDecorator(
decoration: InputDecoration(
),
child:FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
final album = snapshot.data;
final results = album.results;
return DropdownButtonHideUnderline(
child: DropdownButton<Results>(
isExpanded: true,
items: results.map((result) {
return DropdownMenuItem<Results>(
value: result,
child: Text('${result.modelName}'),
);
}).toList(),
onChanged: (album ) {
// selected album
setState(() {
_selected = album;
});
},
value: _selected,
));
} else
return CircularProgressIndicator();
}),
);
}),
I tried to set hinttext, but it does not work.

Here is how you can add a hint to your DropdownButton:
hint: Container(
width: 150,
child: Text(
"Select car",
style: TextStyle(color: Colors.grey),
textAlign: TextAlign.end,
),
),

Related

Flutter SwitchListTile Changing State on AllItems

I am trying to use the SwitchTileList to show all my categories and toggle them on/off however it seems to either not change state/toggle or it will toggle all of them together.
At the moment the code below the showdefault items are on as should be and the rest are off, however it will not toggle any of them at the moment.
return FutureBuilder(
future: amenityCategories,
builder:
(BuildContext context, AsyncSnapshot<AmenityCategories> snapshot) {
if (snapshot.hasData) {
return ListView(
padding: EdgeInsets.zero,
children: [
SizedBox(
height: 85.0,
child: DrawerHeader(
child: Text(
'Show/Hide Map Pins',
style: new TextStyle(fontSize: 18.0, color: Colors.white),
),
decoration: const BoxDecoration(
color: Colors.green,
),
),
),
SizedBox(
height: double.maxFinite,
child: ListView.builder(
itemCount: snapshot.data!.categories.length,
itemBuilder: (context, index) {
bool toggle = false;
if (snapshot.data!.categories[index].showbydefault == 1) {
toggle = true;
}
return SwitchListTile(
title: Text(
snapshot.data!.categories[index].categoryname),
value: toggle,
onChanged: (bool val) {
if (val != toggle) {
setState(() {
toggle = !toggle;
});
}
});
},
),
),
],
);
}
return Container();
});
}
You must use a separate variable for each individual ListTile. Give your category an additional variable isActive and work with it.
onChanged: (bool val) {
if (val != snapshot.data!.categories[index].isActive) {
setState(() {
snapshot.data!.categories[index].isActive = !snapshot.data!.categories[index].isActive;
});
}

TextField value only updated when hovered

I have a Category TextField with a controller. The controller value is updated onChange of a Product Dropdown. What I expect is upon onChange the value of the categoryField should be updated. However, I can only see the update on the TextField once I hover on it.
Category TextField
var productCategory = Column(
children: [
TextField(
controller: categoryController,
enabled: false,
focusNode: categoryFocusNode,
),
const Padding(
padding: EdgeInsets.all(5.0), child: SizedBox(width: 200)),
],
);
Product Dropdown onChange
void onChange<Product>(prod) {
BlocProvider.of<InvoiceCubit>(context).updateProduct(prod);
categoryController.text = prod.category.categoryName.toString();
}
I have finally figured it out. Since I am using a rxdart stream, I created both the Product dropdown and Category on the StreamBuilder. Then I created the categoryController within the builder itself with text value from the product category. Below is my code:
var productDropdownField = StreamBuilder<Product>(
stream: BlocProvider.of<InvoiceCubit>(context).productStream,
builder: (context, snapshot) {
final categoryController = TextEditingController();
categoryController.text = snapshot.hasData
? snapshot.data!.category.categoryName
: "";
var productCategory = Column(
children: [
CustomTextField(
labelText: "Category",
controller: categoryController,
enabled: false,
),
const Padding(
padding: EdgeInsets.all(10.0),
child: SizedBox(width: 200)),
],
);
return StreamBuilder<Object>(
stream:
BlocProvider.of<InvoiceCubit>(context).priceStream,
builder: (context, snapshot) {
return Column(
children: [
BlocBuilder<ProductsCubit, ProductsState>(
builder: (context, state) {
if (state is ProductsLoaded) {
List<DropdownMenuItem<Product>>
dropDownItems = state.products
.map((e) => DropdownMenuItem<Product>(
value: e,
child: Text(
e.productName,
style: const TextStyle(
color: Colors.black,
fontWeight:
FontWeight.w900),
),
))
.toList();
if (invoiceItem == null &&
prodSelected == false) {
onChange<Product>(state.products.first);
prodSelected = true;
}
return CustomDropdown<Product>(
labelText: "Product",
value:
BlocProvider.of<InvoiceCubit>(context)
.getProduct(),
items: dropDownItems,
context: context,
onChanged: onChange,
);
}
return const SizedBox(
width: 100, child: Text("Error"));
},
),
productCategory,
],
);
});
});
categoryController won't be updated via onChange but will be updated based on the snapshot.data

FormBuilderDropdown broke inside futurebuilder

I put the FormBuilderDropdown inside FutureBuilder so when I press on it will rebuild infinitely and will not enter the FormBuilderDropdown if anyone can give me a proper solution
and this is my code
FutureBuilder(
future: widget.fetchBrands(yearValue),
builder:
(BuildContext context, AsyncSnapshot snapshot) {
var snap = snapshot.data;
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
if (yearValue == null) {
return Container(
child: Text(
"Select the manufacture year to Select Brand",
style: TextStyle(
color: Colors.red[800],
fontSize: 16,
),
overflow: TextOverflow.visible,
),
);
}
return Center(
child: new Text('Error'),
);
}
snap.forEach((key, brand) {
brands.add(brand['make']);
});
brands = brands.toSet().toList();
brands.sort();
return
FormBuilderDropdown(
key: ValueKey("Brands"),
name: "Brands",
items: brands
.map(
(e) => DropdownMenuItem<String>(
child: Text(e),
value: e,
),
)
.toList(),
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(
context,
)
],
),
decoration: InputDecoration(
labelText: 'Brands',
labelStyle:
Theme.of(context).textTheme.display1,
),
hint: Text('Select Brand'),
allowClear: true,
onChanged: (value) {
setState(() {
brandValue = value;
carValue = null;
carsName.clear();
});
},
);
},
),
before the new version of FormBuilder, this works well
I do temp solution that after I get the data I remove the future and build a new one without future but this solution not good enough because it takes two tap to open the dropdown
future: widget.fetchBrands(yearValue),
This means, every time build is called, you will start over and fetch the brands. You only want to fetch them once, yo you should only call the method once.
You want this line in initState where it runs only once:
brandsFuture = widget.fetchBrands(yearValue);
and then use that one future in your build method:
future: brandsFuture,
obviously you need to declare brandsFuture as a field in your state class.

Flutter:my DropdownButton value is not taken?

my working with DropdownButton and i facing a problem my value is not taken
is show me null
my code is below
SizedBox(
height: 60.0,
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("Category").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text("Please wait");
var length = snapshot.data.documents.length;
DocumentSnapshot ds = snapshot.data.documents[length - 1];
return new DropdownButton(
items: snapshot.data.documents.map((
DocumentSnapshot document) {
return DropdownMenuItem(
child: new Text(document.data["name"]));
}).toList(),
value: category,
onChanged: (value) {
print(value);
},
hint: new Text("Category"),
style: TextStyle(color: Colors.black),
);
}
),
),
You should read more about StatefulWidget, here you have documentation: https://flutter.io/tutorials/interactive/
To fix your issue just update your category variable and refresh the state.
UPDATE
looks like you forget the value for the item also.
SizedBox(
height: 60.0,
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("Category").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text("Please wait");
var length = snapshot.data.documents.length;
DocumentSnapshot ds = snapshot.data.documents[length - 1];
return new DropdownButton(
items: snapshot.data.documents.map((
DocumentSnapshot document) {
return DropdownMenuItem(
value: document.data["name"],
child: new Text(document.data["name"]));
}).toList(),
value: category,
onChanged: (value) {
print(value);
setState(() {
category = value;
});
},
hint: new Text("Category"),
style: TextStyle(color: Colors.black),
);
}
),
),

How to refresh an AlertDialog in Flutter?

Currently, I have an AlertDialog with an IconButton. The user can click on the IconButton, I have two colors for each click. The problem is that I need to close the AlertDialog and reopen to see the state change of the color icon. I want to change the IconButton color immediately when the user clicks it.
Here is the code:
bool pressphone = false;
//....
new IconButton(
icon: new Icon(Icons.phone),
color: pressphone ? Colors.grey : Colors.green,
onPressed: () => setState(() => pressphone = !pressphone),
),
Use StatefulBuilder to use setState inside Dialog and update Widgets only inside of it.
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
TextButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},
);
Use a StatefulBuilder in the content section of the AlertDialog. Even the StatefulBuilder docs actually have an example with a dialog.
What it does is provide you with a new context, and setState function to rebuild when needed.
The sample code:
showDialog(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0; // Declare your variable outside the builder
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
return Column( // Then, the content of your dialog.
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
// Whenever you need, call setState on your variable
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
And as I mentioned, this is what is said on the showDialog docs:
[...] The widget returned by the builder does not share a context with the location
that showDialog is originally called from. Use a StatefulBuilder or a
custom StatefulWidget if the dialog needs to update dynamically.
This is because you need to put your AlertDialog in its own StatefulWidget and move all state manipulation logic on the color there.
Update:
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return MyDialog();
});
},
)));
}
}
class MyDialog extends StatefulWidget {
#override
_MyDialogState createState() => new _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
Color _c = Colors.redAccent;
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
color: _c,
height: 20.0,
width: 20.0,
),
actions: <Widget>[
FlatButton(
child: Text('Switch'),
onPressed: () => setState(() {
_c == Colors.redAccent
? _c = Colors.blueAccent
: _c = Colors.redAccent;
}))
],
);
}
}
First you need to use StatefulBuilder. Then i am setting _setState variable, which even could be used outside StatefulBuilder, to set new state.
StateSetter _setState;
String _demoText = "test";
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
_setState is used same way as setState method. For example like this:
_setState(() {
_demoText = "new test text";
});
If you're separating your data from the UI via View Models and using the Provider package with ChangeNotifier, you'll need to include your current model like so within the widget calling the dialog:
showDialog(context: context, builder: (dialog) {
return ChangeNotifierProvider.value(
value: context.read<ViewModel>(),
child: CustomStatefulDialogWidget(),
);
},
Note that there may be a cleaner way to do this but this worked for me.
Additional info regarding Provider: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
});
Not sure if this is best practice, but I solved the issue of updating both the dialog state and the content state by wrapping the setState functions, after using the top answer to add state to the dialog:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, newSetState) { // Create a "new" state variable
return AlertDialog(
content: DropdownButton(
value: listItem.type,
items: allItems
onChanged: (value) {
newSetState(() {
setState(() {
// HERE SET THE STATE TWICE
// Once with the "new" state, once with the "old"
});
});
})
),
);
}
);
}
),
In fact, you can use StatefullBuilder but the problem is that when you use this widget you cant change the state of the base screen! Prefer to navigate to a new screen in order to use setState.
I was stuck with this issue.You have to Change the name of setState to any Other name and pass this set state to all sub functions.
This will update your Dialog ui on time.
return StatefulBuilder(
builder: (context, setStateSB) {
return AlertDialog(
title: Text("Select Circle To Sync Data!" ,style: TextStyle(color: Colors.white),),
content: Column(
children: [
Text("Select Division!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DivisionName_firstValue,
items: _DivisionName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black)),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DivisionName_firstValue = newValue!;
if(sync_DivisionName_firstValue !="Select Division Name"){
print("sync_DivisionName_firstValue$sync_DivisionName_firstValue");
_getDistrictName(sync_DivisionName_firstValue,setStateSB);
}else{
refreashDivisionName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select District!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DistrictName_firstValue,
items: _DistrictName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DistrictName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name"){
print("sync_DistrictName_firstValue$sync_DistrictName_firstValue");
_getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,setStateSB);
}else{
refreashDistrictName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Tehsil!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_TehsilName_firstValue,
items: _TehsilName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_TehsilName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name"){
print("sync_TehsilName_firstValue$sync_TehsilName_firstValue");
_getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,setStateSB);
}else{
refreashTehsilName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Rating Area Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_RatingAreaName_firstValue,
items: _RatingAreaName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_RatingAreaName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name" && sync_RatingAreaName_firstValue != "Select Rating Area Name"){
print("sync_RatingAreaName_firstValue$sync_RatingAreaName_firstValue");
_getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue,setStateSB);
}else{
refreashWardCircleName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Ward Circle Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_circle_name_firstValue,
items: _circle_name_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_circle_name_firstValue = newValue!;
print("sync_circle_name_firstValue$sync_circle_name_firstValue");
// if(sync_circle_name_firstValue != "Select Ward Circle Name"){
//
// _getWardCircleName(sync_RatingAreaName_firstValue);
// }else{
//
// }
});
},
)),
),
],
),
),
]),
backgroundColor:Color(0xFFEC9F46),
actions: [
okButton,SyncButton
],
);
},
);
One of the Inner Funciton is like this.
Future<void> refreashDivisionName( StateSetter setInnerState) async {
final List<String> _division_name = await getDivisionNameList();
final List<String> _district_name_list = await getDistrictName(sync_DivisionName_firstValue);
final List<String> _tehsil_name_list = await getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue);
final List<String> _rating_area_name_list = await getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue);
final List<String> _ward_circle_name_list = await getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue);
setInnerState(() {
_division_name.insert(0, "Select Division Name");
_DivisionName_list = _division_name;
sync_DivisionName_firstValue = _DivisionName_list[0];
_district_name_list.insert(0, "Select District Name");
_DistrictName_list = _district_name_list;
sync_DistrictName_firstValue = _DistrictName_list[0];
_tehsil_name_list.insert(0, "Select Tehsil Name");
_TehsilName_list = _tehsil_name_list;
sync_TehsilName_firstValue = _TehsilName_list[0];
_rating_area_name_list.insert(0, "Select Rating Area Name");
_RatingAreaName_list = _rating_area_name_list;
sync_RatingAreaName_firstValue = _RatingAreaName_list[0];
_ward_circle_name_list.insert(0, "Select Ward Circle Name");
_circle_name_list = _ward_circle_name_list;
sync_circle_name_firstValue = _circle_name_list[0];
});
}
I hope you under Stand.
base on Andris's answer.
when dialog share the same state with parent widget, you can override parent widget's method setState to invoke StatefulBuilder's setState, so you don't need to call setState twice.
StateSetter? _setState;
Dialog dialog = showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
// set the function to null when dialo is dismiss.
dialogFuture.whenComplete(() => {_stateSetter = null});
#override
void setState(VoidCallback fn) {
// invoke dialog setState to refresh dialog content when need
_stateSetter?.call(fn);
super.setState(fn);
}
Currently to retrieve the value of Dialog I use
showDialog().then((val){
setState (() {});
print (val);
});
Example
1st screen
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AddDespesa();
}).then((val) {
setState(() {});
print(val);
}
);
}
2nd screen
AlertDialog(
title: Text("Sucesso!"),
content: Text("Gasto resgristrado com sucesso"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
Will be printed true,