Placing dropdownbutton selections into a to-do list - flutter

I am very new to Flutter/Dart, so please forgive my ignorance and please explain everything to me like I am a five year old.
I have a screen on my app where I would like users to have two options: enter text using a textfield or select an option from a dropdown menu. Whichever they choose will be placed on a to-do list.
However, I can't seem to figure out how to get the selection from the DropdownButton widget to be saved to the to-do list the same way a textfield entry would be.
Below is my code for the screen where new items are entered.
import 'package:provider/provider.dart';
import 'task_data.dart';
class AddTaskScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
String newTaskTitle = '';
String _dropdownValue = '';
return Container(
color: const Color(0xFF757575),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
),
padding: const EdgeInsets.symmetric(horizontal: 60.0, vertical: 30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const Text(
'Add a Task',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blue,
fontSize: 30.0,
fontWeight: FontWeight.w500,
),
),
TextField(
autofocus: true,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 25.0,
),
onChanged: (newText) {
newTaskTitle = newText;
},
),
TaskSelect(),
const SizedBox(
height: 20.0,
),
MaterialButton(
onPressed: () {
Provider.of<TaskData>(context, listen: false)
.addTask(newTaskTitle);
Navigator.pop(context);
},
color: Colors.blue,
child: const Text(
'Add',
style: TextStyle(
fontSize: 20.0,
),
),
textColor: Colors.white,
height: 50.0,
)
],
),
),
);
}
}
class TaskSelect extends StatefulWidget {
const TaskSelect({Key? key}) : super(key: key);
#override
State<TaskSelect> createState() => _TaskSelectState();
}
class _TaskSelectState extends State<TaskSelect> {
String dropdownValue = 'Select a task';
String newTaskTitle = '';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.blueAccent),
underline: Container(
height: 2,
color: Colors.blueAccent,
),
onChanged: (String? newValue) {
setState(() {dropdownValue = newValue!;});
newTaskTitle = dropdownValue;
},
items: <String>[
'Task 1',
'Task 2',
'Task 3',
]
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}

Your problem is how to access the value selected from the other class
you can achieve that in many ways
one is to make the widget accept a variable (which is your titleText) and when it's changed in that class (widget) (TaskSelcted) it will change the variable value in the other class (widget)(AddTaskScreen)
class TaskSelect extends StatefulWidget {
//Telling the class you will get a value of string when ever someone used ue
String? taskTitle;
TaskSelect({Key? key, required this.taskTitle}) : super(key: key);
#override
State<TaskSelect> createState() => _TaskSelectState();
}
.
//Telling the class here you are being used, this is the value you are promised to get. you can change its value as i tell you
TaskSelect(newTaskTitle),
const SizedBox(
height: 20.0,
),
.
onChanged: (String? newValue) {
setState(() {dropdownValue = newValue!;});
//Change the variable you took to this value
widget.textTitle = dropdownValue;
}

Related

Flutter there should be exactly one item with [DropdownButton]'s value: Dubai. Either zero or 2 or more [DropdownMenuItem]s were detected

I amm using two languages in my flutter, English and Arabic, when user select his city(cities will show in DropdownMenu) while registering in English language it works fine, he can update his profile by changing his city but when same user change language from English to Arabic and go to his profile section I get error.
How can I resolve this issue?
There should be exactly one item with [DropdownButton]'s value: Dubai. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value 'package:flutter/src/material/dropdown.dart': Failed assertion: line 1580 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem<T> item) { return item.value == value; }).length == 1'
This error popup when English user change to Arabic language, and same when Arabic user change to English language.
Here is my Profile update code:
class Profile extends StatefulWidget {
final Function(Map<String, dynamic>) callBack;
const Profile(this.callBack, {Key? key}) : super(key: key);
#override
State<Profile> createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
List<String> cities = [
LanguageStringKeys.instance.abuDhabi.tr,
LanguageStringKeys.instance.dubai.tr,
LanguageStringKeys.instance.sharjah.tr,
LanguageStringKeys.instance.ajman.tr,
LanguageStringKeys.instance.ummAlQuwain.tr,
LanguageStringKeys.instance.rasAlKhaimah.tr,
LanguageStringKeys.instance.fujairah.tr,
];
String selectedCity = Get.put(AppController()).loginResponse?.data?.user?.address;
var nameController = TextEditingController();
var tradeController = TextEditingController();
var phoneController = TextEditingController();
var bioController = TextEditingController();
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
nameController.text = Get.put(AppController()).loginResponse?.data?.user?.firstName ?? "";
tradeController.text = Get.put(AppController()).loginResponse?.data?.user?.tradeLicense ?? "";
phoneController.text = Get.put(AppController()).loginResponse?.data?.user?.phone ?? "";
selectedCity = Get.put(AppController()).loginResponse?.data?.user?.address ?? "";
bioController.text = Get.put(AppController()).loginResponse?.data?.user?.bio ?? "";
});
}
#override
Widget build(BuildContext context) {
return BackgroundImage(
image: const AssetImage('assets/images/enjyback.png'),
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: Dimensions.width20, vertical: Dimensions.height10),
child: Column(
children: [
AppTextField(
textController: nameController,
labelText: LanguageStringKeys.instance.name.tr,
labelColor: Colors.white,
borderColor: Colors.white,
textColor: Colors.white,
inputType: TextInputType.name,
),
SizedBox(height: Dimensions.height15),
AppTextField(
textController: tradeController,
labelText: LanguageStringKeys.instance.tradeLicenseNo.tr,
labelColor: Colors.white,
borderColor: Colors.white,
textInputFormatter: 7,
textColor: Colors.white,
inputType: TextInputType.number,
),
SizedBox(height: Dimensions.height15),
AppTextField(
textController: phoneController,
labelText: LanguageStringKeys.instance.phoneNumber.tr,
labelColor: Colors.white,
borderColor: Colors.white,
textColor: Colors.white,
inputType: TextInputType.number,
),
SizedBox(height: Dimensions.height15),
ButtonTheme(
alignedDropdown: true,
child: DropdownButtonFormField(
isExpanded: true,
borderRadius: BorderRadius.circular(Dimensions.height10),
iconEnabledColor: Colors.white,
selectedItemBuilder: (BuildContext context) {
return cities
.map((e) => Text(
selectedCity,
style: const TextStyle(color: Colors.white),
))
.toList();
},
decoration: textFormFieldsDecoration(
LanguageStringKeys.instance.city.tr,
Colors.white,
Colors.white,
),
items: cities
.map((item) => DropdownMenuItem(
value: item,
child: SmallText(text: item, color: Colors.black),
))
.toList(),
value: selectedCity,
onChanged: (item) => setState(() => selectedCity = item as String)),
),
SizedBox(height: Dimensions.height15),
AppTextField(
textController: bioController,
labelText: LanguageStringKeys.instance.bio.tr,
labelColor: Colors.white,
textColor: Colors.white,
maxLines: 6,
borderColor: Colors.white,
inputType: TextInputType.name,
),
SizedBox(height: Dimensions.height15),
ElevatedButton(
onPressed: () {
Provider.of<UserProfileService>(context, listen: false).updateUserProfile(context, {}, {
"name": nameController.text,
"trade_license": tradeController.text,
"phone": phoneController.text,
"address": selectedCity,
"bio": bioController.text,
"image": image,
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimensions.radius10)),
minimumSize: Size.fromHeight(Dimensions.height40),
),
child: SmallText(text: LanguageStringKeys.instance.save.tr, color: Colors.grey),
),
SizedBox(height: Dimensions.height15),
],
),
),
),
),
),
),
);
}
}

why does printing the text value in here brings up null?

So i'm trying to print out the value that's newTaskTitle when i press the flat button but it always brings up null , although when i print it in the textField it shows correctly , any idea why ?
for example on the onChanged for the text field , i try to print after assigning the newTaskTitle value to the newText , it prints the value
if I then try to print the value on the onPressed in the flat button , it print null .
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:todoey/models/task.dart';
class AddTaskScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
String newTaskTitle;
return Container(
color: Color(0xff757575),
child: Container(
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Add Task',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 30.0,
),
),
TextField(
autofocus: true,
textAlign: TextAlign.center,
onChanged: (newText) {
newTaskTitle = newText;
},
),
SizedBox(
height: 10.0,
),
FlatButton(
color: Colors.lightBlueAccent,
onPressed: () {
//addTaskCallback(newTaskTitle);
print(newTaskTitle);
},
child: Text(
'Add',
style: TextStyle(
color: Colors.white,
),
),
)
],
),
),
);
}
}
You need to update the state of the widget.
The widget you are using is stateless, that is, it does not manipulate states, a solution (There are many) is to transform your widget from stateless to stateful.
Then you could use a SetState or ValueNotifier to modify the value.
Example:
import 'package:flutter/material.dart';
class AddTaskScreen extends StatefulWidget {
AddTaskScreen({Key key}) : super(key: key);
#override
_AddTaskScreenState createState() => _AddTaskScreenState();
}
class _AddTaskScreenState extends State<AddTaskScreen> {
String newTaskTitle = '';
#override
Widget build(BuildContext context) {
return Container(
color: Color(0xff757575),
child: Container(
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Add Task',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 30.0,
),
),
TextField(
autofocus: true,
textAlign: TextAlign.center,
onChanged: (newText) {
newTaskTitle = newText;
},
),
SizedBox(
height: 10.0,
),
FlatButton(
color: Colors.lightBlueAccent,
onPressed: () {
setState(() {});
},
child: Text(
'Add',
style: TextStyle(
color: Colors.white,
),
),
)
],
),
),
);
}
}
You are trying to change the state of stateless widget. Convert your widget to StatefulWidget by clicking on Stateless and then pressing Ctrl + ., select convert to Stateful widget and then test app again.

Creating a custom Flutter DropDown button as a separate widget

I am working on a Flutter project, which has multiple dropdown buttons on different pages. I have created a custom dropdown button and it's working fine. But when I try to make it a separate widget (to use in other pages) in a file having some errors. below is the code I tried.
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<Activities> _activities = [
Activities(id: 1, icon: Icons.place, title: 'Travel'),
Activities(id: 2, icon: Icons.food_bank, title: 'Eat'),
];
List<DropdownMenuItem<Activities>> activityListDropDownItems = [];
Activities selectedActivity;
int selectedActivityId = 0;
String selectedActivityTitle = '';
IconData selectedActivityIcon = Icons.addchart;
List<DropdownMenuItem<Activities>> buildActivityList(List activities) {
List<DropdownMenuItem<Activities>> items = [];
for (Activities activity in activities) {
items.add(
DropdownMenuItem(
value: activity,
child: Row(
children: [
Text(
'${activity.title}',
style: TextStyle(
color: Colors.red,
),
),
],
),
),
);
}
return items;
}
onChangeActivityListDropDownItem(Activities selected) {
setState(() {
selectedActivity = selected;
selectedActivityId = selected.id;
selectedActivityTitle = selected.title;
selectedActivityIcon = selected.icon;
});
}
#override
void initState() {
activityListDropDownItems = buildActivityList(_activities);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom DropDown'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$selectedActivityTitle'),
SizedBox(height: 10.0),
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey,
width: 0.3,
),
borderRadius: BorderRadius.all(
Radius.circular(
30.0,
),
),
),
child: Row(
children: [
SizedBox(width: 10.0),
Icon(
selectedActivityIcon,
color: Colors.red.withOpacity(0.7),
),
SizedBox(width: 10.0),
Expanded(
child: DropdownButton(
hint: Text(
'Activity',
style: TextStyle(),
),
isExpanded: true,
value: selectedActivity,
items: activityListDropDownItems,
onChanged: onChangeActivityListDropDownItem,
underline: Container(),
),
)
],
),
),
SizedBox(height: 20.0),
//MyDropDown(
// value: selectedActivity,
// items: activityListDropDownItems,
// onChanged: onChangeActivityListDropDownItem,
//),
],
),
),
);
}
}
class MyDropDown extends StatelessWidget {
final List<DropdownMenuItem> items;
final IconData icon;
final dynamic value;
final String hintText;
final ValueChanged onChanged;
const MyDropDown(
{Key key,
#required this.items,
this.icon,
this.value,
this.hintText,
this.onChanged})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey,
width: 0.3,
),
borderRadius: BorderRadius.all(
Radius.circular(
30.0,
),
),
),
child: Row(
children: [
SizedBox(width: 10.0),
Icon(
icon,
color: Colors.red.withOpacity(0.7),
),
SizedBox(width: 10.0),
Expanded(
child: DropdownButton(
hint: Text(
hintText,
style: TextStyle(),
),
isExpanded: true,
value: value,
items: items,
onChanged: onChanged,
underline: Container(),
),
)
],
),
);
}
}
class Activities {
final int id;
final IconData icon;
final String title;
Activities({#required this.id, #required this.icon, #required this.title});
}
when I use the MyDropDown shown an error. How to pass value and onChanged to DropdownButton?
MyDropDown(
value: selectedActivity,
items: activityListDropDownItems,
onChanged: onChangeActivityListDropDownItem,
),
Your value expects a datetype dynamic while your passing Activities
class MyDropDown<T> extends StatelessWidget {
final T value;
const MyDropDown({Key key, this.value}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
Declare Object type
MyDropDown<Activies>(
value: selectedActivity,
items: activityListDropDownItems,
onChanged: onChangeActivityListDropDownItem,
),

flutter: dropdown item programmatically unselect problem

I use two dropdown menu here.
Both data come from API. And second's one data depends on first dropdown items. Thats mean when I select an item from first menu then data will come on second dropdown's. And It's changing dynamically.
I face a problem here. When I change a items from first dropdown, second dropdown show me a error.
Like this -
Before
After Change City value
Here is my Code for two dropdown
// Choose City
CustomDropDownMenu(
items: allCity.map((list) {
return DropdownMenuItem(
child: Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
"${list["name"]}",
style: TextStyle(),
),
),
onTap: () {
setState(() {
isVisible = false;
});
getWeight(list["id"]).then((value) => {
setState(() {}),
});
print(list["id"]);
FocusScope.of(context).unfocus();
print(weightList.length);
},
value: list["id"].toString(),
);
}).toList(),
value: _city,
hint: "Choose City",
onChanged: (value) {
setState(() {
this._city = value;
});
},
),
// Weight
CustomDropDownMenu(
hint: "Select Weight",
value: _weight,
items: [
DropdownMenuItem(child: Text("Select Weight")),
...weightList.map((list) {
return DropdownMenuItem(
child: Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
"${list["weight"]}",
style: TextStyle(),
),
),
onTap: () {
FocusScope.of(context).unfocus();
},
value: list["id"].toString(),
);
}).toList()
],
onChanged: (value) {
setState(() {
_weight = value;
});
},
),
Here is CustomDropDown() class
.. class CustomDropDownMenu extends StatelessWidget { final String hint; final dynamic value;
final Function onChanged; final Function onSaved; final List<DropdownMenuItem<dynamic>> items;
const CustomDropDownMenu({
Key key,
this.hint,
this.onChanged,
this.onSaved,
this.items,
this.value, }) : super(key: key); #override Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
return Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(
Radius.circular(60),
),
),
child: Card(
shape: StadiumBorder(),
elevation: 5,
child: DropdownButtonFormField(
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
hint,
textAlign: TextAlign.end,
),
),
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
),
value: value,
onChanged: onChanged,
onSaved: onSaved,
items: items,
),
)); } }
That's why I want to unselect second's on dropdown menu items programmatically, but not find a solution. Please some one help me.
set _weight value null on city change
Convert your CustomDropDownMenu into StatefulWidget and implement below code:
#override
void didUpdateWidget(covariant AppDropDown oldWidget) {
super.didUpdateWidget(oldWidget);
if (!listEquals(oldWidget.items, widget.items)) {
value = null;
}}

Flutter, textfield value is getting empty when virtual keyboard is hidden

I am trying to create a login and register page for my app, I'm using the stateful widget on both screens.
after filling up the registration or login form and hiding the keyboard to press the register or login button, I'm getting the "string is empty" result, also on onPressed, I've tried to print my email and password in the console but I'm getting an empty field. But, if I try the same with my virtual keyboard still open on my virtual device, I'm able to print out the string, as far as I can understand the error is happening only when the keyboard is hidden.
this is my input field class
class RoundedInputField extends StatefulWidget {
final String hintText;
final ValueChanged<String> onChanged;
final Color color;
final bool boolean;
RoundedInputField({
Key key,
this.hintText,
this.onChanged,
this.color,
this.boolean = false,
}) : super(key: key);
#override
_RoundedInputFieldState createState() => _RoundedInputFieldState();
}
class _RoundedInputFieldState extends State<RoundedInputField> {
#override
Widget build(BuildContext context) {
return TextFieldContainer(
child: TextFormField(
onChanged: widget.onChanged,
obscureText: widget.boolean,
decoration: InputDecoration(
hintText: widget.hintText,
border: InputBorder.none,
),
),
);
}
}
class TextFieldContainer extends StatefulWidget {
final Widget child;
final Color color;
const TextFieldContainer({
Key key,
this.child,
this.color: Colors.white,
}) : super(key: key);
#override
_TextFieldContainerState createState() => _TextFieldContainerState();
}
class _TextFieldContainerState extends State<TextFieldContainer> {
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
width: size.width * 0.8,
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(29),
),
child: widget.child,
);
}
}
and I call RoundedInputField
RoundedInputField(
hintText: "Email",
onChanged: (val) {
email = val;
},
this is my button for registering, currently im only printing the values
Container(
margin: EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: FlatButton(
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 40),
color: Colors.white,
onPressed: () async {
print(email);
print(password);
},
child: Text(
'login',
style: GoogleFonts.montserrat(
color: HexColor(studentPrimaryColour), fontSize: 20),
),
),
),
),
this is my login screen
class StudentLoginScreen extends StatefulWidget {
StudentLoginScreen();
#override
_StudentLoginScreenState createState() => _StudentLoginScreenState();
}
class _StudentLoginScreenState extends State<StudentLoginScreen> {
#override
Widget build(BuildContext context) {
final AuthService _authService = AuthService();
Size size = MediaQuery.of(context).size;
String email = '';
String password = '';
return Scaffold(
backgroundColor: HexColor(studentPrimaryColour),
body: SafeArea(
child: ListView(
children: <Widget>[
SizedBox(
height: 25.0,
),
HeadingText(
text: 'Login',
size: 60.0,
color: Colors.white,
),
SizedBox(
height: 25.0,
),
RoundedInputField(
hintText: "Email",
onChanged: (val) {
email = val;
},
),
SizedBox(
height: 5.0,
),
RoundedInputField(
hintText: "Password",
boolean: true,
onChanged: (val) {
password = val;
},
),
SizedBox(
height: 15.0,
),
Container(
margin: EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: FlatButton(
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 40),
color: Colors.white,
onPressed: () async {
print(email);
print(password);
},
child: Text(
'login',
style: GoogleFonts.montserrat(
color: HexColor(studentPrimaryColour), fontSize: 20),
),
),
),
),
SizedBox(
height: 15.0,
),
InkWell(
onTap: () {
Navigator.pushNamed(context, '/studentRegisterScreen');
},
child: HeadingText(
text: 'register?',
color: Colors.white,
size: 10,
),
),
],
),
),
);
}
}
Inside your login screen, you declared and initialised both properties email and password inside the build method. What this essentially means is, that as soon as your login widget gets rebuilded (for example when hiding the keyboard since Flutter has to recalculate size and so on) both properties are initialised again with ''.
Thats what StatefulWidget are also for - defining properties as part of the state, without being part of the build cycle. In other words, change it up to this:
class StudentLoginScreen extends StatefulWidget {
StudentLoginScreen();
#override
_StudentLoginScreenState createState() => _StudentLoginScreenState();
}
class _StudentLoginScreenState extends State<StudentLoginScreen> {
String email = '';
String password = '';
#override
Widget build(BuildContext context) {
...
}