So I making an app in which I have DropdownButton. I want to get the value parameter from SharedPreferences. But SharedPreferences.getInstance returns Future, not String. How could I return a String instead of Future?
This is a function for reading from SharedPreferences
_read() async {
final prefs = await SharedPreferences.getInstance();
final key = 'cur_r';
final value = prefs.getString(key) ?? "";
print('read: $value');
return value;
}
Here is the dropdown Button code:
body: Center(
child: DropdownButton<String>(
value: __read(),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
_save(newValue);
_read();
});
},
items: ['one', 'two', 'three']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
This is a better approach of doing it.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// make all of them instance variable
String dropdownValue;
SharedPreferences prefs;
final _key = 'cur_r';
#override
void initState() {
super.initState();
_read(); // read in initState
}
_read() async {
prefs = await SharedPreferences.getInstance();
setState(() {
dropdownValue = prefs.getString(_key) ?? "one"; // get the value
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("")),
body: Center(
child: DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
prefs.setString(_key, dropdownValue); // save value to SharedPreference
},
items: ['one', 'two', 'three'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
}
}
Related
How can I select/check only one checkbox to be checked at time?
And below is my code
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Checkbox(
checkColor: Colors.white,
value: isChecked,
onChanged: (bool value) {
setState(() {
isChecked = value;
// ignore: unnecessary_statements
passData(certId);
});
},
),
],
)),
Option1 - Using a map to maintain the state
Create a map:
final Map<int, bool> _state = {};
then, check if the value for that index is true/false:
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _state[index] ?? false,
onChanged: (value) {
setState(() {
_state[index] = value!;
});
},
title: Text(_data[index].text),
);
});
Option 2 - using a model:
class CheckBoxModel {
bool isChecked = false;
String text = "";
CheckBoxModel({required this.isChecked, required this.text});
}
and then, generate a List of 30 widgets:
final _data = List.generate(
30, (index) => CheckBoxModel(isChecked: false, text: "Item $index"));
Now, use a ListView.builder and based on the index, to update the corresponding value:
class Testing extends StatefulWidget {
const Testing({Key? key}) : super(key: key);
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
#override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _data[index].isChecked,
onChanged: (value) {
setState(() {
_data[index].isChecked = value!;
});
},
title: Text(_data[index].text),
);
});
}
}
See also
Expansion tile trailing icon updates all in list on interaction with one tile. How can I only change the icon for the expanded tile?
i have a DropDownButton, which is filled from an SQLite DB which is ok for my app for now. But after choosing an entry, the DropDownButton didnt show the choosen entry, just the hint. To check my entry i try to fill a textfield also with the choosen entry, but this isnt changed too. Here is my code for the DropDownButton:
List<DropdownMenuItem<String>> teamList;
DropdownMenuItem selectedTeam;
DropdownButton(
hint: Text("Choose"),
value: selectedTeam,
onChanged: (value) {
setState(() {
_teamController.text = value.name;
selectedTeam = value;
});
},
items: teamList,
),
actually i fill my teamList with a codesnippet inside the initstate:
super.initState();
teamList = [];
db.getData().then((listMap) {
listMap.map((map) {
print(map.toString());
return getDropDownWidget(map);
}).forEach((dropDownMenuItem) {
teamList.add(dropDownMenuItem);
});
setState(() {});
});
and with this:
DropdownMenuItem<String> getDropDownWidget(Map<String, dynamic> map) {
return DropdownMenuItem<String>(
value: map['team'],
child: Text(map['team']),
);
}
in my dbhelper-file i have this code:
Future<List<Map<String, dynamic>>> getData() async {
var dbClient = await db;
return await dbClient.rawQuery('SELECT team FROM teamTable');
}
Hey Thomas Check out this example :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SampleApp(),
debugShowCheckedModeBanner: false,
);
}
}
class SampleApp extends StatefulWidget {
#override
_SampleAppState createState() => _SampleAppState();
}
class _SampleAppState extends State<SampleApp> {
List<String> teamList = ['Sample', 'Sample2', 'Sample3', 'Sample4'];
String selectedTeam;
TextEditingController _teamController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Your heading'),
),
body: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: _teamController,
),
new DropdownButton<String>(
items: teamList.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
value: selectedTeam,
hint: Text('Choose'),
onChanged: (value) {
setState(() {
_teamController.text = value;
selectedTeam = value;
print('This is the selected value: $selectedTeam');
});
},
),
],
)));
}
}
Let me know if it works.
I have a ListView.builder that builds a certain amount of widgets depending on user input. Each widget has their own specific name and has a DropDownMenu. I save this value with the corresponding name of the widget. It saves it correctly. However, when I try and read the data and create a new list from it, this error appears: [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The method '[]' was called on null.
'course' is a list. I am using the shared preferences import. When you tap the flat button, it should build the new list, but it is not. Could you explain to me why this is not working please?
This is code in the main app.
void main() {
runApp(Hemis());
}
class Hemis extends StatefulWidget {
#override
_HemisState createState() => _HemisState();
}
class _HemisState extends State<Hemis> {
_read() async {
final prefs = await SharedPreferences.getInstance();
for(int i = 0; i < course.length; i++) {
listMarks[i].name = course[i].name;
listMarks[i].mark = prefs.getInt(course[i].name) ?? 0;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
ListView.builder(
itemCount: course.length,
itemBuilder: (context, index) {
return ModuleListItem(
name: '${course[index].name}',
credits: course[index].credits,
);
},
),
FlatButton(
onPressed: () {
_read();
for(int i = 0; i < course.length; i++) {
print('${listMarks[i].name}: ${listMarks[i].mark}');
}
},
),
],
),
)
)
);
}
}
The widget that is being built.
final percentage = List<String>.generate(100, (i) => "$i");
class ModuleListItem extends StatefulWidget {
const ModuleListItem ({ Key key, this.name, this.credits }): super(key: key);
final String name;
final int credits;
#override
_ModuleListItemState createState() => _ModuleListItemState();
}
class _ModuleListItemState extends State<ModuleListItem> {
String dropdownValue;
bool isSwitched = false;
_save() async {
final prefs = await SharedPreferences.getInstance();
final key = '${widget.name}';
final value = int.parse(dropdownValue);
prefs.setInt(key, value);
print('saved $value');
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
DropdownButton<String>(
value: dropdownValue,
icon: Icon(Icons.keyboard_arrow_down),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: percentage.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
if(isSwitched == true) {
_save();
}
print(isSwitched);
});
},
),
],
),
);
}
}
I'm trying to create an AlertDialog that will receive a Future-List or a List of the data from the Dropdownbuttons and the TextField that are inside of these Alert. In my App when I call these function for the AlertDialog, there will be 3 DropdownButtons and 1 TextField so the User can select the info that he wants, and then when he presses "OK" from the AlerDialog, the data he selected will be inside of the list, so I can use it with Firestore.
This is my AlertDialog:
Future<List> createAlertDialog(BuildContext context){
return showDialog(context: context, builder: (ctx){
return AlertDialog(
title: Text('Text'),
content: Column(
children: [
DropdownButton(
value: iconNameSelected,
items: iconlistdrop,
hint: Text('Select Icon'),
onChanged: (value) {
iconNameSelected = value;
setState(() {});
},
),
DropdownButton(
value: activelabelSelected,
items: activelistdrop,
hint: Text('Select Active label'),
onChanged: (value1) {
activelabelSelected = value1;
setState(() {});
},
),
DropdownButton(
value: inactivelabelSelected,
items: inactivelistdrop,
hint: Text('Select InActive label'),
onChanged: (value2) {
inactivelabelSelected = value2;
setState(() {});
},
),
TextField(
),
],
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: (){
final values = [];
Navigator.of(ctx).pop(values);
},
)
],
);
});
}
Here's how I attempted to call it inside my InkWell widget:
createAlertDialog(context).then((value){
printf(value[0]);
printf(value[1]);
printf(value[2]);
printf(value[3]);
}
Here's some extra stuff from the Data I have inserted inside the DropdownButtons:
List<DropdownMenuItem<String>> iconlistdrop = [];
List<DropdownMenuItem<String>> activelistdrop = [];
List<DropdownMenuItem<String>> inactivelistdrop = [];
String iconNameSelected = null;
String activelabelSelected = null;
String inactivelabelSelected = null;
void loadIcon () {
iconlistdrop = [];
iconlistdrop.add(DropdownMenuItem(
child: Text('LightBulb'),
value: 'lightbulbOutline',
));
iconlistdrop.add(DropdownMenuItem(
child: Text('Lock'),
value: 'lock',
));
iconlistdrop.add(DropdownMenuItem(
child: Text('Check'),
value: 'check',
));
}
void activelbl () {
activelistdrop = [];
activelistdrop.add(DropdownMenuItem(
child: Text('On'),
value: 'On',
));
activelistdrop.add(DropdownMenuItem(
child: Text('Locked'),
value: 'Locked',
));
}
void inactivelbl () {
inactivelistdrop = [];
inactivelistdrop.add(DropdownMenuItem(
child: Text('Off'),
value: 'Off',
));
inactivelistdrop.add(DropdownMenuItem(
child: Text('Locked'),
value: 'Unlocked',
));
}
loadIcon();
activelbl();
inactivelbl();
My Class:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
var temperature;
var humidity;
Future getWeather() async {
http.Response response = await
http.get('http://api.openweathermap.org/data/2.5/weather?
q=Curitiba&units=metric&appid=8c1ce29a0b974e97562564d892cd5a97');
var results = jsonDecode(response.body);
setState(() {
this.temperature = results['main']['temp'];
this.humidity = results['main']['humidity'];
});
}
#override
void initState () {
this.getWeather();
super.initState();
}
#override
Widget build(BuildContext context) {
final AuthService _auth = AuthService();
final user = Provider.of<User>(context);
Future getSubCollection(){
return Firestore.instance.collection('dadosusuarios').document(user.uid).collection('buttons').getDocuments();
}
I would use a map for the values and separate the dialog to another widget and give it a constructor in case you might want it to have initial values.
import 'package:flutter/material.dart';
class MyAlertDialog extends StatefulWidget {
final Map<String, dynamic> initialValues;
const MyAlertDialog({
Key key,
this.initialValues,
}) : super(key: key);
#override
_MyAlertDialogState createState() => _MyAlertDialogState();
}
class _MyAlertDialogState extends State<MyAlertDialog> {
Map<String, dynamic> _values;
TextEditingController _controller;
#override
initState() {
super.initState();
_values = widget.initialValues ??
{'input1': 'One', 'input2': 'Two', 'input3': 'Free', 'input4': 'Four'};
_controller = TextEditingController(text: _values['input4']);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Text'),
content: Column(
children: [
DropdownButton(
value: _values['input1'],
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
hint: Text('Select Icon'),
onChanged: (value1) {
setState(() {
_values['input1'] = value1;
});
},
),
DropdownButton(
value: _values['input2'],
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
hint: Text('Select Active label'),
onChanged: (value2) {
setState(() {
_values['input2'] = value2;
});
},
),
DropdownButton(
value: _values['input3'],
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
hint: Text('Select InActive label'),
onChanged: (value3) {
setState(() {
_values['input3'] = value3;
});
},
),
TextField(
controller: _controller,
),
],
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: () {
_values['input4'] = _controller.text.trim();
Navigator.of(context).pop(_values);
},
)
],
);
}
}
Here i look if there is a value passed from the constructor. If not put some defaults. Update the map with each user input change and finally once the dialog popped return the map. Using map here is better in my opinion and would make pushing the values to Firestore easier.
var result = await showDialog(
context: context,
builder: (ctx) {
return MyAlertDialog(initialValues: /* Your default values if exist*/ );
});
print(result);
I am trying to ask the user to select the type of item in the first dropdown and then select from its corresponding available colours in the second dropdown. However, when after I have selected a colour (i.e. white) and now want to switch to another item that does not have this colour, an error is thrown:
"There should be exactly one item with [DropdownButton]'s value: white. \nEither zero or 2 or more
[DropdownMenuItem]s were detected with the same value"
Please help, I have already tried to setState at various places to update the values but this error still occurs.
The following is my code snippet:
StreamBuilder<QuerySnapshot>(
stream: mainItemsSnapshots,
builder: (context, snapshot) {
if (snapshot.hasError) return Text("Error");
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
{
List<DropdownMenuItem> dropdownMenuItems =
snapshot.data.documents
.map((DocumentSnapshot mainItem) {
return new DropdownMenuItem<String>(
value: mainItem.documentID,
child: Text(mainItem.documentID),
);
}).toList();
return DropdownButtonFormField<String>(
items: dropdownMenuItems,
onChanged: (String value) {
if (value != tempMainItemType) {
setState(() {
tempMainItemType = value;
tempItemColorsList.clear();
tempItemColorsList = [];
tempMainItemColor = null;
});
}
if (tempItemColorsList.isEmpty && value != null) {
tempItemColorsList = snapshot.data.documents
.where((element) => element.documentID == value)
.first
.data["colors"]
.keys
.map((color) => color.toString())
.toList()
.cast<String>();
}
setState((){});
},
onSaved: (String value) {
_order.mainItemType = value;
},
value: tempMainItemType,
);
}
}
},
),
// Main color
if (tempItemColorsList?.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: spacingGeneral),
child: textFieldLabel(context, "Main color"),
),
if (tempItemColorsList?.isNotEmpty)
DropdownButtonFormField(
items: tempItemColorsList.map((String color) {
return new DropdownMenuItem<String>(
value: color,
child: Text(color),
);
}).toList(),
onSaved: (String value) {
_order.mainColor = value;
},
value: tempMainItemColor,
onChanged: (String value) {
setState(() {
tempMainItemColor = value;
});
},
),
This maybe too late, but you can create a Map<String, List<String>> where the keys are the items of the first dropdown list, and the value will be the items of second dropdown list.
Here, I created a state that stores the selected item of the first dropdown list. Then I used it to map the items of the second dropdown list.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SampleDD(),
);
}
}
class SampleDD extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DoubleDropdown(
items: <String, List<String>>{
'dark colors': ['black', 'gray', 'brown'],
'light colors': ['white', 'yellow', 'green'],
},
onChangedFirst: (val) => print('Selected first: $val'),
onChangedSecond: (val) => print('Selected second: $val'),
),
),
);
}
}
class DoubleDropdown extends StatefulWidget {
DoubleDropdown({
#required this.items,
#required this.onChangedFirst,
#required this.onChangedSecond,
});
final Map<String, List<String>> items;
final ValueChanged<String> onChangedFirst;
final ValueChanged<String> onChangedSecond;
#override
_DoubleDropdownState createState() => _DoubleDropdownState();
}
class _DoubleDropdownState extends State<DoubleDropdown> {
String selectedKey;
#override
void initState() {
super.initState();
selectedKey = widget.items.keys.first;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
_buildFirstDropdown(),
_buildSecondDowndown(),
],
);
}
Widget _buildFirstDropdown() => DropdownButtonFormField<String>(
items: widget.items.keys.map((e) {
return DropdownMenuItem<String>(
child: Text(e),
value: e,
);
}).toList(),
onChanged: (val) {
setState(() => selectedKey = val);
widget.onChangedFirst(val);
},
value: selectedKey,
);
Widget _buildSecondDowndown() => DropdownButtonFormField<String>(
items: widget.items[selectedKey].map((e) {
return DropdownMenuItem<String>(
child: Text(e),
value: e,
);
}).toList(),
onChanged: widget.onChangedSecond,
value: widget.items[selectedKey].first,
);
}