Related
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),
],
),
),
),
),
),
),
);
}
}
Im trying to create a trainings app for myself.
I have created a app that can add more textfields everytime a button is pushed.
The problem im having is all the fields have the same value and i cant enter two diffrent
I have tried a couple of things and the closest i got was when i could do diffrent values but not save it.
My code is the following:
class EventEditingPage extends StatefulWidget {
final Event? event;
const EventEditingPage({Key? key, this.event}) : super(key: key);
#override
_EventEditingPageState createState() => _EventEditingPageState();
}
class _EventEditingPageState extends State<EventEditingPage> {
final _formKey = GlobalKey<FormState>();
final setsController = TextEditingController();
final repsController = TextEditingController();
final loadController = TextEditingController();
late DropdownButton Exercise;
#override
void initState() {
super.initState();
final event = widget.event!;
exercise = event.Exercise;
setsController.text = event.Sets;
repsController.text = event.Reps;
loadController.text = event.Load;
}
}
#override
void dispose() {
setsController.dispose();
repsController.dispose();
loadController.dispose();
super.dispose();
}
int numberOfTextFields = 1;
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: CloseButton(),
actions: buildEditingActions(),
backgroundColor: Colors.red,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(12),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("text", style: TextStyle(fontSize: 24)),
SizedBox(height: 12),
Text("\ntext", style: TextStyle(fontSize: 24)),
for(int i = 0 ; i < numberOfTextFields ; i++)
buildExerciseComplete(),
buildAddMore(),
],
),
),
),
);
List<Widget> buildEditingActions() => [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Colors.transparent, shadowColor: Colors.transparent),
onPressed: saveForm,
icon: Icon(Icons.done),
label: Text('Save'),
)
];
Widget buildExerciseComplete() => Container(
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: Color.fromARGB(255, 255, 200, 200),
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
width: 2.0,
),
),
height: 50,
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Expanded(child: buildExercise()),
Expanded(child: buildSets()),
Expanded(child: buildReps()),
Expanded(child: buildLoad()),
],
),
),
],
),
);
Widget buildSets() => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Sets',
),
onFieldSubmitted: (_) => saveForm,
validator: (sets) =>
sets != null && sets.isEmpty ? 'Sets cannot be empty' : null,
controller: setsController,
);
Widget buildReps() => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Reps',
),
onFieldSubmitted: (_) => saveForm,
validator: (reps) =>
reps != null && reps.isEmpty ? 'Reps cannot be empty' : null,
controller: repsController,
);
Widget buildLoad() => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
labelText: 'Load',
border: OutlineInputBorder(),
),
onFieldSubmitted: (_) => saveForm,
validator: (load) =>
load != null && load.isEmpty ? 'Load cannot be empty' : null,
controller: loadController,
);
String exercise = 'Squat';
Widget buildExercise() => DropdownButton<String>(
value: exercise,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.black),
onChanged: (String? newValue) {
setState(() {
exercise = newValue!;
});
},
items: <String>['Squat', 'Deadlift', 'Bench']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
Widget buildAddMore() => ElevatedButton(
onPressed: () {
setState((){
numberOfTextFields++;
_textEditingControllers.add(TextEditingController());
});
},
child: Text('Add new exercise'),
);
Future saveForm() async {
final isValid = _formKey.currentState!.validate();
if (isValid) {
final event = Event(
Exercise: exercise,
Sets: setsController.text,
Reps: repsController.text,
Load: loadController.text);
final isEditing = widget.event != null;
final provider = Provider.of<EventProvider>(context, listen: false);
if (isEditing) {
provider.editEvent(event, widget.event!);
Navigator.of(context).pop();
} else {
provider.addEvent(event);
}
Navigator.of(context).pop();
}
}
}
class Event {
final Exercise;
final String Sets;
final String Reps;
final String Load;
final Color backgroundColor;
const Event({
required this.Sets,
required this.Reps,
required this.Load,
this.backgroundColor = Colors.lightGreen,
});
}
Im pretty the reason has something to do with TextEditingController()
Im hoping that somebody could help me with the solution to my problem :-)
It seems you are building all of the fields with the same controller, so all will have the same value. You should make a list of controllers and add controllers.
Declare variables like this:
final List<TextEditingController> setsController = [];
final List<TextEditingController> repsController = [];
final List<TextEditingController> loadController = [];
Then Create Controller:
createControllers() {
for (var i = 0; i < numberOfTextFields; i++) {
setsController.add(TextEditingController());
repsController.add(TextEditingController());
loadController.add(TextEditingController());
}
}
Use the controller in respective text Field:
controller: repsController[index]
Call the create controller method at initState().
I guess you can handle the value retrieve and dispose of the controllers from here.
I don't know your save logic, so I commented it out.
class _EventEditingPageState extends State<EventEditingPage> {
final _formKey = GlobalKey<FormState>();
final List<TextEditingController> setsController = [];
final List<TextEditingController> repsController = [];
final List<TextEditingController> loadController = [];
// late DropdownButton Exercise;
List<String> exceriseItems = ['Squat', 'Deadlift', 'Bench'];
List<String> selectedExercise = [];
int numberOfTextFields = 1;
#override
void initState() {
super.initState();
final event = widget.event!;
createControllers();
selectedExercise[0] = event.Exercise != "" ? event.Exercise : exceriseItems[0];
setsController[0].text = event.Sets;
repsController[0].text = event.Reps;
loadController[0].text = event.Load;
}
#override
void dispose() {
setsController.forEach((TextEditingController element) {
element.dispose();
});
// Or simply clear the array.
// todo : repsController dispose();
// todo : loadController dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: const CloseButton(),
actions: buildEditingActions(),
backgroundColor: Colors.red,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text("text", style: TextStyle(fontSize: 24)),
const SizedBox(height: 12),
const Text("\ntext", style: TextStyle(fontSize: 24)),
for (int i = 0; i < numberOfTextFields; i++)
buildExerciseComplete(i),
buildAddMore(),
],
),
),
),
);
List<Widget> buildEditingActions() => [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Colors.transparent, shadowColor: Colors.transparent),
onPressed: saveForm,
icon: const Icon(Icons.done),
label: const Text('Save'),
)
];
Widget buildExerciseComplete(int idx) => Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 255, 200, 200),
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
width: 2.0,
),
),
height: 50,
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Expanded(child: buildExercise(idx)),
Expanded(child: buildSets(idx)),
Expanded(child: buildReps(idx)),
Expanded(child: buildLoad(idx)),
],
),
),
],
),
);
Widget buildSets(int idx) => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 15),
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Sets',
),
onFieldSubmitted: (_) => saveForm,
validator: (sets) =>
sets != null && sets.isEmpty ? 'Sets cannot be empty' : null,
controller: setsController[idx],
);
Widget buildReps(int idx) => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 15),
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Reps',
),
onFieldSubmitted: (_) => saveForm,
validator: (reps) =>
reps != null && reps.isEmpty ? 'Reps cannot be empty' : null,
controller: repsController[idx],
);
Widget buildLoad(int idx) => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 15),
decoration: const InputDecoration(
labelText: 'Load',
border: const OutlineInputBorder(),
),
onFieldSubmitted: (_) => saveForm,
validator: (load) =>
load != null && load.isEmpty ? 'Load cannot be empty' : null,
controller: loadController[idx],
);
Widget buildExercise(int idx) => DropdownButton<String>(
value: selectedExercise[idx],
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.black),
onChanged: (String? newValue) {
setState(() {
selectedExercise[idx] = newValue!;
});
},
items: exceriseItems
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
Widget buildAddMore() => ElevatedButton(
onPressed: () {
setState(() {
numberOfTextFields++;
createControllers();
});
},
child: const Text('Add new exercise'),
);
createControllers() {
for (var i = 0; i < numberOfTextFields; i++) {
setsController.add(TextEditingController());
repsController.add(TextEditingController());
loadController.add(TextEditingController());
}
selectedExercise.add(exceriseItems[0]);
}
Future saveForm() async {
final isValid = _formKey.currentState!.validate();
if (isValid) {
// final event = Event(
// Exercise: exercise,
// Sets: setsController.text,
// Reps: repsController.text,
// Load: loadController.text);
final isEditing = widget.event != null;
// final provider = Provider.of<EventProvider>(context, listen: false);
// if (isEditing) {
// provider.editEvent(event, widget.event!);
//
// Navigator.of(context).pop();
// } else {
// provider.addEvent(event);
// }
//
// Navigator.of(context).pop();
}
}
}
I am design a screen in which I need to opening a drop down list when user click on textfield till now I am using CupertinoActionSheetAction sheet which is correctly working now I need to replace it with DropdownButton but when I am doing it is not displaying on screen I don't know why.
Here is my code focus on line number 136 (function for CupertinoActionSheetAction is working code) and line 138 where (function for drop not working code) according to me may be it due to build context but I m not sure how to use 'build context' in this context can any one help me out to fix that.
Thanks in advance, And any suggestion for making my code more reusable will be helpful as well :)
import 'dart:ffi';
import 'package:fleet_management/AppContants/Contants.dart';
import 'package:fleet_management/Controllers/NewFuelExpenseController.dart';
import 'package:fleet_management/Models/NewFuelExpenseModel.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AddFuelView extends StatefulWidget {
const AddFuelView({Key? key}) : super(key: key);
#override
_AddFuelView createState() {
return _AddFuelView();
}
}
// MyTripHistoryView
class _AddFuelView extends State<AddFuelView> {
NewFuelExpenseModel fuelExpense =
NewFuelExpenseController().getNewFuelExpense();
DateTime _selectedDate = DateTime.now();
String dropdownvalue = 'Item 1';
// List of items in our dropdown menu
var items = [
'Item 1',
'Item 2',
'Item 3',
'Item 4',
'Item 5',
];
TextStyle _titleTextStyle = const TextStyle();
TextStyle _valueTextStyle = const TextStyle();
final GlobalKey newFuelExpenseFormField = GlobalKey<FormState>();
final TextEditingController _dateEditingController = TextEditingController();
final TextEditingController _priceTextEditingController =
TextEditingController();
final TextEditingController _gallonsTextEditingController =
TextEditingController();
final TextEditingController _totalTextEditingController =
TextEditingController();
final TextEditingController _odometerTextEditingController =
TextEditingController();
final TextEditingController _fuelTypeTextEditingController =
TextEditingController();
final TextEditingController _vendorTextEditingController =
TextEditingController();
#override
void initState() {
super.initState();
_titleTextStyle =
const TextStyle(fontSize: 16, fontWeight: FontWeight.w600);
_valueTextStyle = const TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black38);
_dateEditingController.text =
"${_selectedDate.day}-${_selectedDate.month}-${_selectedDate.year}";
}
#override
Widget build(BuildContext context) {
var scaffold = Scaffold(
appBar: AppBar(
title: Text(StringsConstants.newFuelExpense),
backgroundColor: AppColorsConstants.primaryColor,
actions: <Widget>[
FlatButton(
textColor: Colors.white,
onPressed: () {
print("SAVE Button Clicked");
},
child: Text("Save", style: _titleTextStyle),
),
],
),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: newFuelExpenseFormField,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
showDateRow(context),
addTextFieldInput(
StringsConstants.priceGallon,
"\u{20B9}0.00",
false,
_priceTextEditingController,
TextInputType.datetime,
null,
null),
addTextFieldInput(
StringsConstants.gallons,
"",
false,
_gallonsTextEditingController,
TextInputType.number,
null,
null),
addTextFieldInput(
StringsConstants.total,
"\u{20B9}0.00",
false,
_totalTextEditingController,
TextInputType.number,
null,
null),
addTextFieldInput(
StringsConstants.odometer,
"",
false,
_odometerTextEditingController,
TextInputType.number,
null,
null),
// show action sheet
addTextFieldInput(
StringsConstants.fuelType,
"",
true,
_fuelTypeTextEditingController,
TextInputType.none,
null, () {
// Working. - >>>> HERE <<<<<
showActionSheet(context);
// Not Working >>>> HERE <<<<< Uncomment following function before
// showDropDown();
}),
addTextFieldInput(
StringsConstants.vendor,
"",
false,
_vendorTextEditingController,
TextInputType.text,
null,
null),
const SizedBox(
height: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
print("Submit button pressed!");
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24),
primary: AppColorsConstants.primaryColor,
textStyle:
const TextStyle(fontWeight: FontWeight.bold),
),
child: Text(StringsConstants.submit)),
ElevatedButton(
onPressed: () {
print("Cancel button pressed!");
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24),
primary: AppColorsConstants.primaryColor,
textStyle:
const TextStyle(fontWeight: FontWeight.bold),
),
child: Text(StringsConstants.cancel))
],
)
],
),
),
),
),
),
);
return scaffold;
}
void showDropDown() {
DropdownButton<String>(
items: <String>['A', 'B', 'C', 'D'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
);
}
Future<dynamic> showActionSheet(BuildContext context) {
return showCupertinoModalPopup(
context: context,
builder: (BuildContext context) => CupertinoActionSheet(
title: const Text('Choose Options'),
actions: <Widget>[
CupertinoActionSheetAction(
child: const Text('Oil'),
onPressed: () {
_fuelTypeTextEditingController.text = "Oil";
Navigator.pop(context, 'Oil');
},
),
CupertinoActionSheetAction(
child: const Text('Petrol'),
onPressed: () {
_fuelTypeTextEditingController.text = "Petrol";
Navigator.pop(context, 'Petrol');
},
),
CupertinoActionSheetAction(
child: const Text('diesel'),
onPressed: () {
_fuelTypeTextEditingController.text = "diesel";
Navigator.pop(context, 'diesel');
},
)
],
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context, 'Cancel');
},
)),
);
}
Container addTextFieldInput(
String title,
String initialValue,
bool isEditable,
TextEditingController textfieldController,
TextInputType keyboardType,
FormFieldValidator<String>? validator,
GestureTapCallback? tabCallback,
) {
return Container(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 8),
child: SizedBox(
child: TextFormField(
onTap: tabCallback,
keyboardType: keyboardType,
cursorHeight: 16,
cursorWidth: 1.4,
readOnly: isEditable,
controller: textfieldController,
validator: validator,
decoration: InputDecoration(
isDense: true,
floatingLabelStyle:
TextStyle(color: AppColorsConstants.primaryColor),
labelText: title,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: AppColorsConstants.primaryColor, width: 1.5),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black38, width: 1.5),
),
border: const OutlineInputBorder()),
style: const TextStyle(fontSize: 18),
cursorColor: Colors.black38,
showCursor: true,
autofocus: true,
),
),
);
}
Container showDateRow(BuildContext context) {
return Container(
child: TextFormField(
onTap: () => {_selectDate(context)},
cursorHeight: 16,
cursorWidth: 1.4,
readOnly: true,
enableInteractiveSelection: false,
controller: _dateEditingController,
decoration: InputDecoration(
isDense: true,
labelStyle: const TextStyle(color: Colors.orangeAccent),
labelText: StringsConstants.date,
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.orangeAccent, width: 1.5),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black38, width: 1.5),
),
border: const OutlineInputBorder()),
style: const TextStyle(fontSize: 18),
cursorColor: Colors.black38,
showCursor: true,
autofocus: true,
),
);
}
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(1990),
lastDate: DateTime.now());
if (picked != null && picked != _selectedDate) {
setState(() {
_selectedDate = picked;
this._dateEditingController.text =
"${_selectedDate.day}-${_selectedDate.month}-${_selectedDate.year}";
});
}
}
}
For DropDown in flutter you need
an initail value,
a list to be shown just
String selectedvalue='myvalue';
/// be sure you add the variable value in the 0 index of you list other wise it will thrown error or exception..
List<String> myList=[
'myvalue',/// the value is same as variable value
'othe value', ....
];
DropDownButton(
onChanged: (resultValue){
/// you can get selected value from here
},
items: myList.map((e){
retrun DropdownMenuItem(
value: e,
child: Text(e));
}).toList(),
value: selectedvalue,
),
You can use DropdownButtonFormField in place of TextFormField and can make some style related changes per your requirement.
class MyWidget extends StatelessWidget {
String selectedValue = "USA";
List<DropdownMenuItem<String>> get dropdownItems{
List<DropdownMenuItem<String>> menuItems = [
DropdownMenuItem(child: Text("USA"),value: "USA"),
DropdownMenuItem(child: Text("Canada"),value: "Canada"),
DropdownMenuItem(child: Text("Brazil"),value: "Brazil"),
DropdownMenuItem(child: Text("England"),value: "England"),
];
return menuItems;
}
#override
Widget build(BuildContext context) {
return DropdownButtonFormField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2),
borderRadius: BorderRadius.circular(20),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2),
borderRadius: BorderRadius.circular(20),
),
filled: true,
fillColor: Colors.blueAccent,
),
dropdownColor: Colors.blueAccent,
value: selectedValue,
onChanged: (String? newValue) {
selectedValue = newValue!;
},
items: dropdownItems);
}
}
I'm working on flutter project and came across a problem. the textfield supposed to take a value from controller which work perfectly but onChanged function wont work. However it is not working.i must retype value to show results .
how can i get result from onchanged when my textfield brings the value ?
code:
// search function that call api
class _SearchPageTState extends State<SearchPageT> {
GlobalState _store = GlobalState.instance;
List<dynamic> searchResults = [];
searchT(value) async {
SearchServicet.searchApiT(value).then((responseBody) {
List<dynamic> data = jsonDecode(responseBody);
setState(() {
data.forEach((value) {
searchResults.add(value);
});
});
});
}
#override
///////
//code textfield search onchanged
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
centerTitle: true,
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
autofocus: true,
controller: TextEditingController()
..text = '${_store.get('num')}',
onChanged: (value) {
searchResults.clear();
searchCodeighniterT(value);
},
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 25.0),
labelText: 'number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: null,
),
),
),
),
SizedBox(
height: 10.0,
),
ListView.builder(
physics: ScrollPhysics(),
shrinkWrap: true,
itemCount: searchResults.length,
itemBuilder: (BuildContext context, int index) {
return buildResultCard(context, searchResults[index]);
},
),
],
),
),
);
}
//here where shows the result
Widget buildResultCard(BuildContext context, data) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
new Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/bgwlgo.png"), fit: BoxFit.cover),
),
margin: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
new Card(
child: ListTile(
title: Text(
"DATE:",
),
subtitle: Text(
data['DATETIME'].toString(),
),
),
),
new Card(
child: ListTile(
title: Text(
"TRAITEMENT:",
),
subtitle: Text(
data['TRAITEMENT_DETAIL'].toString()
),
),
),
new Padding(
padding: EdgeInsets.all(9.0),
),
],
),
),
Divider(
color: Colors.black,
)
],
),
);
}
As discussed and understood in the comments, here are a couple of possible solutions for the issue.
If you are using a stateful Widget and need a controller for your TextFormField, you can use it to set the text on the TextFormField. Otherwise, you can just use the initialValue property:
class TextFieldIssue64061076 extends StatelessWidget {
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children:[
Text('Page 1'),
TextFormField(
controller: _textEditingController,
),
FlatButton(
child: Text('Submit'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return SecondPage64061076(text: _textEditingController.text);
}
)),
),
],
);
}
}
class SecondPage64061076 extends StatefulWidget {
final String text;
SecondPage64061076({
this.text
});
#override
_SecondPage64061076State createState() => _SecondPage64061076State();
}
class _SecondPage64061076State extends State<SecondPage64061076> {
final TextEditingController _textEditingController = TextEditingController();
#override
void initState() {
// You can either use a text controller to set the text on the text field
// or the initialValue below
if(widget.text != null && widget.text != ''){
_textEditingController.text = widget.text;
methodToCallOnChanged(widget.text);
}else{
_textEditingController.text = '';
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 2'),
TextFormField(
controller: _textEditingController,
onChanged: (value) => methodToCallOnChanged(value),
// As mentioned you could just use the initial value, it you don't need
// a Stateful Widget
// initialValue: widget.text ?? '',
),
]
),
);
}
void methodToCallOnChanged(data){
print('I was called onChange or when the view opened and there was data');
}
}
Widget build(BuildContext context) {
bool isRecording = false;
bool isShowSendButton = false;
/// make sure textcontroller and any variabloes or booleans are not defined
///in the build method
}
You can't be using the constructor of the TextEditingController and assign it to the controller.
The controller of the TextField only takes the variable assigned to the TextEditingController. Check the following code.
controller:theNameOfYourController
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 10),
child: TextFormField(
onChanged: (value){
messageFieldController.text=value;
},
//controller: messageFieldController,
minLines: 1,
maxLines: 2,
textCapitalization: TextCapitalization.sentences,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(Icons.add, color: Colors.blue),
),
suffixIcon: processing == true
? const CircularProgressIndicator()
: IconButton(
onPressed: () {
try {
/*for (int i = 0;
i < widget.selectedMembers.length;
i++) {
sendMessage(
widget.selectedMembers[i].number);
}*/
for (int i = 0;
i < widget.selectedGroups.length;
i++) {
if (widget.selectedGroups.isNotEmpty) {
sendMessageToGroup(
widget.selectedGroups[i].id,
widget.selectedGroups[i].groupName);
}
}
} catch (err) {
Fluttertoast.showToast(
msg: 'Unable to send Message');
setState(() {
processing = false;
});
}
},
icon:
const Icon(Icons.send, color: Colors.blue),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
),
),
),
)
I have a flutter web app (which may become a desktop app in the future) that contains a dialog box. The dialog allows the user to edit the properties of a model object that contains a list of elements. The user should be able to edit the list by adding or deleting elements to the list. So the dialog contents are of variable height -- it depends on the number of elements in the list.
I am having trouble creating a layout that dynamically resizes appropriately. What I want is for the dialog to grow as items are added to the list, up to the maximum size that would fit on the device's screen. If the contents grow larger than this, the elements in the list should be scrollable.
I've attached two screenshots of what I have working at the moment; the first has a list with only two items, which is easily displayed. The second is the same dialog with many items showing the overflow.
Here is the code for the Dialog:
Widget build(BuildContext context) {
return AlertDialog(
actions: [
FlatButton(
child: const Text('CANCEL'),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: const Text('OK'),
onPressed: () =>
Navigator.of(context).pop<Modifier>(_createModifier()),
),
],
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Damage Editor'),
Divider(),
columnSpacer,
DiceSpinner(
onChanged: (value) => setState(() => _dice = value),
initialValue: _dice,
textFieldWidth: 90.0,
),
columnSpacer,
Container(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
border: Border.all(color: Colors.grey),
),
child: DropdownButton<DamageType>(
underline: Container(),
value: _type,
items: _damageTypeItems(),
onChanged: (value) => setState(() => _type = value),
),
),
columnSpacer,
SwitchListTile(
value: _direct,
onChanged: (state) => setState(() => _direct = state),
title: Text(_direct ? 'Internal (Direct)' : 'External (Indirect)'),
),
if (!_direct) ...<Widget>[
columnSpacer,
CheckboxListTile(
value: _explosive,
onChanged: (state) => setState(() => _explosive = state),
title: Text('Explosive'),
),
],
columnSpacer,
DynamicListHeader(
title: 'Enhancements/Limitations',
onPressed: () => setState(() =>
_modifiers.add(TraitModifier(name: 'Undefined', percent: 0))),
),
SingleChildScrollView(
child: ListBody(
children: _enhancementList(),
),
),
],
),
);
}
List<Widget> _enhancementList() {
var list = <Widget>[];
_modifiers.forEach(
(element) {
if (_modifiers.length > 0) list.add(columnSpacer);
list.add(_EnhancerEditor(element, index: _modifiers.indexOf(element),
onChanged: (index, enhancer) {
_modifiers[index] = enhancer;
}));
},
);
return list;
}
typedef TraitModifierCallback = void Function(int, TraitModifier);
class _EnhancerEditor extends StatefulWidget {
_EnhancerEditor(this.enhancer, {this.onChanged, this.index});
final TraitModifier enhancer;
final TraitModifierCallback onChanged;
final int index;
#override
__EnhancerEditorState createState() => __EnhancerEditorState();
}
class __EnhancerEditorState extends State<_EnhancerEditor> {
TextEditingController _nameController;
TextEditingController _percentController;
bool _validInput = true;
#override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.enhancer.name);
_nameController.addListener(_onChanged);
_percentController =
TextEditingController(text: widget.enhancer.percent.toString());
_percentController.addListener(_onChanged);
}
#override
void dispose() {
_nameController.removeListener(_onChanged);
_nameController.dispose();
_percentController.removeListener(_onChanged);
_percentController.dispose();
super.dispose();
}
void _onChanged() {
String text = _percentController.text.trim();
setState(() {
int value = int.tryParse(text);
_validInput = (value != null);
if (_validInput) {
widget.onChanged(widget.index,
TraitModifier(name: _nameController.text, percent: value));
}
});
}
#override
Widget build(BuildContext context) {
return IntrinsicHeight(
child: Row(
children: [
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Enhancer/Limitation',
border: const OutlineInputBorder(),
),
),
),
rowSmallSpacer,
SizedBox(
width: 80.0,
child: TextField(
controller: _percentController,
textAlign: TextAlign.end,
keyboardType: TextInputType.numberWithOptions(signed: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9\-]'))
],
decoration: const InputDecoration(
suffixText: '%',
labelText: 'Pct',
border: const OutlineInputBorder(),
),
),
),
],
),
);
}
}
It may sound counterintuitive but you should have ListView within Column. You need both.
In dialog Column will deal with dialog size, and ListView will deal with scrolling within.
Column(
mainAxisSize: MainAxisSize.min,
children:[
ListView(padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: ClampingScrollPhysics(),
Ok, here you go ... Simple call of dialog ...
FlatButton(
onPressed: () {
showDialog(
useRootNavigator: false,
context: context,
builder: (BuildContext context) => Test(),
);
},
child: Text('Suppper'),
)
then code of dialog ... it is pretty straight forward ...
all controllers you can place in a map that you will deal with when you add new entries... both adding and removing is super easy ... let me know if further assitance is needed.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
void initState() {
super.initState();
}
Widget rowEntry(_nameController, _percentController) {
return IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Enhancer/Limitation',
border: const OutlineInputBorder(),
),
),
),
SizedBox(
width: 80.0,
child: TextField(
controller: _percentController,
textAlign: TextAlign.end,
keyboardType: TextInputType.numberWithOptions(signed: true),
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9\-]'))],
decoration: const InputDecoration(
suffixText: '%',
labelText: 'Pct',
border: const OutlineInputBorder(),
),
),
),
],
),
);
}
addEntry() async {
entries.add('new value');
setState(() {});
}
List entries = [];
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Container(
padding: EdgeInsets.all(16),
width: 300,
child: ListView(shrinkWrap: true, children: [
Text('here you will put all that is above (+)'),
Container(
margin: EdgeInsets.only(top: 16, bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Exhancements/Limitations'),
InkWell(
onTap: () {
addEntry();
},
child: Icon(Icons.add),
),
],
),
),
ListView.builder(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
scrollDirection: Axis.vertical,
itemCount: entries == null ? 0 : (entries.length),
itemBuilder: (BuildContext context, int index) {
return rowEntry(null, null);
}),
]),
),
);
}
}