i was wondering if you use a DropDownMenu in Flutter there is a possibility to get different values.
For Example:
You have the items ['item1','item2','item3','item4','item5']
then you use a DropDownButton with these items. But for every single item there should be passed a different value so i can work with that later.
So that item1 = 90, item2 = 180, item3 = 270 and so on.
If item1 is selected it should be displayed to the user like that and the app gets the value 90 for example where it can work with later.
I hope you get what i mean and can help me a litte bit.
You can create a map and use it's key for DropDownMenu, While showing use key to show value like
String? dropdownValue;
Map<String, int> dataset = {
"item1": 90,
"item2": 180,
"item3": 270,
};
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: dropdownValue,
focusColor: Colors.white,
items: dataset.keys.map((String value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (v) {
setState(() {
dropdownValue = v;
});
},
),
),
Text(
dropdownValue == null
? "Select Item "
: dataset[dropdownValue!].toString(),
),
There are a lot of ways to do so.
1- Through key-value pairs.
Get your sample for example:
items[{'name':'item1','value':'90'},{'name':'item2','value':'180'},{'name':'item3','value':'270'},{'name':'item4','value':'360'}];
(presumably they are a 360 degree angels :-))
Then you can point to each item like this: items[i]['name'] or items[j]['value'] and so on...
2- Not preferred way: two different lists like this:
itemNames[items1,item2,item3, item4];
itemValues [90,180,270,360];
Then you can pass the itemNames list to your dropdownMenu and whenever an item has been selected you can find it's index and point to the itemsValues' appropriate row. For instance: dropdownMenu item3 selected so the returned index should be 2 (indexes are starting from 0) so you can find the desired value like this: desiredValue = itemsValue[2]; (270)
Related
I want to make an editable TextWidget in flutter but I don't really know how to go around it, I did some research, but still can't find a good solution.
Here's my sample code below.
I have a variable called
int qty = 1;
and so I called the variable in TextWidget
Column(
children: [
Text(
"${qty}",
style: TextStyle(),
)
],
),
I want to have these features that make user tab on the value to change it if they want, upon tap, a pop-up dialog will show to give the user the ability to change the existing value to whatever the user wants.
Please if anyone knows how, please help.
You will need a statfull widget to call setState and make the UI update with the new value stored in your qty variable. (I'am assuming that you are not using any state managment).
I wrote a possible solution for what you need.
Let look into some considerations:
Text will show whatever is in the qty as long we call setState after (or do it inside) we change the value of qty.
You need some widget to detect your tap. If you want to the text be 'clicable' then it should be wraped inside that widget.
The onTap/onPress call back of that widget should show a new widget. For this you can use the already made showDialog() and pass it a Dialog Widget. in here you will put your ui for that.
In some point of that UI you need to introduce the new value. So you can use a simple TextField that will save the introduced value, where you can assign it to qty, without forgetting to call setState! Note that it deal with strings, so you neet to do an int.parse() ou double.parse accordingly to you qty var type.
And I think that's it.
The could be other ways of doing it. This is a good and simple approach for your need.
I wrote a piece of code to help or somelse how is trying to do it:
InkWell(
// can be gesture detector, button, etc
onTap: () => showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
color:
Colors.white60, // change it accordingly to you
height: 80, // change it accordingly to you
width: 200, // change it accordingly to you
child: Column(
children: [
const Text('Change your value here'),
TextField(
decoration:
InputDecoration(hintText: qty.toString()),
onChanged: (insertValue) => setState(() {
qty = int.parse(insertValue);
}),
// you can use other callBack function (like onComplete,
// onSaved), wich is more eficient than calling setState eveytime,
// but you have to do the needed adtaptions. Like onSave
// needs a key to call the save function. is easy just google it.
),
],
)),
)),
child: Text(
"${qty}",
),
),
What you are probably looking is a DropdownButton.
You would have something like this:
int qty = 1;
List<int> listOfValues = [1,2,3,4];
and then in your column you would have
DropdownButton<int>(
// This are the list of items that will appear in your dropdown menu.
// items is all the options you want your users to be able to select from,
// and it take a list of `DropdownMenuItem`. So instead of creating a `DropdownMenuItem`
// for each of the items in `listOfValues`, we iterate through it and return
// a `DropdownMenuItem`
items: listOfValues
.map((item) => DropdownMenuItem<int>(
value: item,
child: Text('$item'),
))
.toList(),
value: qty,
onChanged: (value) {
if (value != null) {
setState(() {
qty = value;
});
}
},
),
For more information on DropDownButton, check the following links:
https://api.flutter.dev/flutter/material/DropdownButton-class.html
https://www.youtube.com/watch?v=K8Y7sWZ7Q3s
Note: In a scenario where you want to increase the quantity of an item, like in a shopping cart, maybe having a button increment qty by 1 would be better.
I have been trying to make an external UI that a user can use to make certain changes to a database(dynamodb) in the cloud. When I select a new value, I want it to show the change that the user wants to make, without actually changing the database. The changes are saved only when I press a button on the appbar. Also when I use setState to rebuild the button, the value doesn't change on the cloud and it also changes the value for all of the buttons in the column(works fine without a setState). The code that I have provided changes the database when I press the save icon, but the drop-down button value stays the same unless I refresh the page. I apologize if I haven't explained my issue clearly enough, this is my first time posting on Stackoverflow, and I'm still learning about how to work with flutter and aws amplify.
body: InteractiveViewer(
constrained: false,
child: DataTable(
columns: [
DataColumn(label: Text('Apt #')),
DataColumn(label: Text('Type')),
DataColumn(label: Text('Availability')),
DataColumn(label: Text('Price')),
DataColumn(label: Text('Area'))
],
rows: aprts.map<DataRow>((element) { //aprts is a list that contains apartment objects.
return DataRow(cells: [
DataCell(Text(element.aptNum.toString())),
DataCell(Text(element.type)),
DataCell(DropdownButton<String>( /// this is the part im having problems wi
value: element.availabily, // gets the value for the availability attribute for the element and stores it into value.
onChanged: (String newValue) {
availValue = newValue; //stores the newValue in a global variable that is used in a function that is used toactually make changes to the database.
tempAvail = element;
},
items: <String>['AVAILABLE', 'SOLD', 'RESERVED']
.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)), // end of problem.
DataCell(TextField(
controller: TextEditingController()
..text = element.price.toString(),
onChanged: (text) {
aptPrice = text;
tempPrice = element;
},
)),
DataCell(TextField(
controller: TextEditingController()..text = element.area,
onChanged: (text) {
aptArea = text;
tempArea = element;
},
)),
]);
}).toList()),
),
What the app looks like. After pressing the button
Use
onChanged: (String newValue) {
setState(() {
availValue = newValue;
tempAvail = element;
}
)
},
because for every change in the UI you must call setState((){})
How to use group button in listView ? In each row, it has two radio button.
The problem now is if I select one of the radio button , the radio button not showing it is selected.
int value;
Map<int, bool> answers = {};
String _radioValue;
String choice;
int count = 0;
...item['questions']
.asMap()
.entries
.map((items) {
count++;
return TableRow(children: [
Padding(
padding: EdgeInsets.all(10),
child: Text(items.value['name'])),
Row(
children: [
Radio(
value: items.value['id'],
groupValue: count,
onChanged: (val) {
setSelectedRadioTile(val);
},
),
Text("Yes"),
Radio(
value: items.value['id'] + count,
groupValue: count,
onChanged: (val) {
setSelectedRadioTile(val);
},
),
Text("N/A")
],
)
]);
}).toList()
setSelectedRadioTile(int val) {
print(val);
setState(() {
count = val;
});
}
Okay well I have built you a working version based on what you provided here. Please keep in mind that it would probably be a good idea to look at the documentation more in-depth so you get a feeling of how some widgets behave and what specific properties (like groupValue) are for. Also keep in mind that the following code is not optimised or whatsoever, I just got it worked out for your case - thinking about how to structure your data overall is some fundamental thing you should take a look at. Maybe try some out some flutter courses which are available or look at some youtube content from known flutter coders. But now back to some code stuff.
I used those properties in my StatefulWidget to work with. Since you use some kind of question map and I don't know how it looks like, I just used something bare bones:
/// Map which has the question ID as its key and the answer from the user (currently true for yes and false for no (or n/a as in your case)
Map<int, bool> _answers = {};
/// Map which holds the information of your questions. Right now only an ID to be able to reference it and the actual question - again very bare bones
Map<String, dynamic> _item = {
'questions': [
{
'id': 0,
'question': 'Is this legit?',
},
{
'id': 1,
'question': 'Oh really?',
},
]
};
Then the method which will be called by the Radio widget once onChanged is triggered:
/// We need to know which question has been answered (therefore the ID) and which (bool) answer has been clicked by the user
_setSelectedRadioTile(int id, bool answer) {
setState(() {
_answers[id] = answer;
});
}
And now the widget part - since your code starts where you iterate over the questions, I also share this part specifically:
/// Since the map itself is typed as <String, dynamic>, accessing 'questions' will return dynamic. Only we, as we know the structure, know that this is indeed a list. Therefore we need to cast this as "List<dynamic>" so we can iterate over it and won't get exceptions
(_item['questions'] as List<dynamic>)
.map(
(question) => TableRow(children: [
Padding(
padding: EdgeInsets.all(10),
child: Text(question['question'])),
Row(
children: [
Radio(
/// The [value] property of Radio tells us, which property is used in [onChanged] - therefore now once this Radio is clicked, true is provided in the [onChanged] callback
value: true,
/// [groupValue] is the value which this Radio is bound to. As you probably know, only one Radio button should be active for a group. So if you have a question which has several possible answers as radio buttons, we only want one of them to be active at a time. Thats what the [groupValue] is for. Since we iterate over all questions and every entity of a question (currently) has two possible answers (Yes and N/A), both those answers are for a specific question - the question with the current ID
groupValue: _answers[question['id']],
onChanged: (answer) {
_setSelectedRadioTile(question['id'], answer);
},
),
Text("Yes"),
Radio(
value: false,
groupValue: _answers[question['id']],
onChanged: (answer) {
_setSelectedRadioTile(question['id'], answer);
},
),
Text("N/A")
],
)
]),
)
.toList(),
This should work on your side once you updated this example to fit with your data model. Again: I advise you to think about how you structure your data generally.
I have a positioned ListViewBuilder inside a Stack attempting to show a dynamic drop down across multiple tiles with different drop down values. This seems to work fine, except for the fact that the values don't seem to clear correctly when the list is updated with new values and refreshed on SetState.
ListView builder code
Positioned(
top: 100,
left: 100,
width: 90,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: dropDownListValues.length,
itemBuilder: (context, index) {
return Tile(
tileId: dropDownListValues[index],
isHeaderTile: false,
uuid: Uuid().v1(),
);
},
),
),
Clear and update list based on which button is pressed.
void _toggleDropDown(List valueList) {
if (dropDownListValues.isEmpty) {
setState(() {
dropDownListValues = valueList;
});
} else {
setState(() {
dropDownListValues.clear();
});
}
}
What I end up getting is the list extends based on the number of items in the drop down, but the values from the previous list carry across from the last list..
Example.
Drop Down list values
Dropdown list 1 = ['one', 'two']
Dropdown list 2 = ['two', 'three', 'four']
What I am seeing is
Dropdown list 1 = ['one', 'two']
Dropdown list 2 = ['one', 'two', 'four']
The if I click on list two drop down first I get the following
Dropdown list 1 = ['two', 'three']
Dropdown list 2 = ['two', 'three', 'four']
Im stumped and have spend hours on this, any idea what I could be doing wrong to cause this refresh issue?
Thanks in advance.
Try using keys so the Flutter Framework can identify changes to your list.
My test doesn't found the item in drop down menu with a value key.
It's works with getText() and the value.
I create a dynamic function to fill every items with a value, a child with Text(value) and a key with Key('sign_$value_item');
This is my full form in the app:
static const menuSigns = <String>[
'aries',
'taurus',
'gemini',
'cancer',
'leo',
'virgo',
'libra',
'scorpio',
'sagittarius',
'capricorn',
'aquarius',
'pisces'
];
final List<DropdownMenuItem<String>> _dropDownMenuSigns = menuSigns
.map<DropdownMenuItem<String>>((String value) => DropdownMenuItem<String>(
key: new ValueKey('sign_$value_item'), // i even try with new Key('sign_$value')
value: value,
child: new Text(value),
))
.toList();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Container(
margin: EdgeInsets.fromLTRB(_hPad, 16.0, _hPad, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Container(
padding: EdgeInsets.fromLTRB(0, 0, 0, 16.0),
width: CustomTheme.customFormSize.width(context),
child: DropdownButton(
key: Key('sign_list'),
isExpanded: true,
value: _sign,
style: CustomTheme.style.dropDownMenu(context),
hint: Text('Choose a sign'),
icon: Icon(Icons.arrow_drop_down_circle),
onChanged: ((newValue) {
setState(() {
_sign = newValue;
});
}),
items: _dropDownMenuSigns,
),
),
),
],
),
),
);
}
And strange things, the test works with the Key if the length of value is very long, for exemple more 10 characters.
This is my test:
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
class AstroSignValidation extends AndWithWorld<FlutterWorld> {
#override
Future<void> executeStep() async {
await FlutterDriverUtils.getText(world.driver, find.text('AstroDay'));
await FlutterDriverUtils.tap(world.driver, find.byValueKey('sign_list')); // open drop down menu is ok
await FlutterDriverUtils.tap(world.driver, find.byValueKey('sign_cancer_item')); // here test not passed
}
RegExp get pattern => RegExp(r"I expect the user enters sign");
}
Edit: This is my feature file :
Feature: Get Astro day
User should be able to get successfully his astro after cliking astro button.
Scenario: User get astro in successfully
Given I expect the "user" 1 sign
And I expect the user enters day
When user hits Show your astro button
Then user should land on result screen
I recreated your case. Instead of using key property on DropdownMenuItem, you need to use it inside it's child, ie, in Text widget. That way, since the flutter driver will look for text to be selected when dropdown menu is open, the key property will come into play when menu items are displayed and then easier to click on whatever option we pass in the test. It worked well. Updated working code below:
final List<DropdownMenuItem<String>> _dropDownMenuSigns = menuSigns
.map<DropdownMenuItem<String>>((String value) => DropdownMenuItem<String>(
// key: new ValueKey('sign_$value'),
value: value,
child: new Text(value, key: Key('sign_$value'),), // use key here on text
))
.toList();
driver test:
class AstroSignValidation extends GivenWithWorld<FlutterWorld> {
#override
Future<void> executeStep() async {
await FlutterDriverUtils.getText(world.driver, find.text('Choose a sign'));
await FlutterDriverUtils.tap(world.driver, find.byValueKey('sign_list')); // open drop down menu is ok
await FlutterDriverUtils.tap(world.driver, find.byValueKey('sign_virgo')); // selects sign properly
print('selected sign');
}
RegExp get pattern => RegExp(r"I expect the user enters sign");
}
And test passes :
Note: I directly used Given statement in feature file and accordingly extended GivenWithWorld class in my test. You'll need to use it per your needs.
Hope this answers your question.
I created custom step definition at
https://gist.github.com/PROGrand/03b7fa0b49642e691148dc010816cc83#file-click_dropdown-dart
Usage in feature file:
...
And I tap the "someMenuItemKey" within the "someDropdownKey" dropdown
...