Flutter DropDownMenu Button update an existing record - flutter

I have been experimenting with my flutter drop down button.
Context of what I am doing.
I have an app that will create a job and give it to an available staff member. I have stored all my staff members in a list for the menu button. I will put the code below to show the creation of the job ticket drop down button. selectedTech is at the top of the program so that's not the issue
String selectedTech = "";
Container(
// margin: EdgeInsets.only(right: 20),
width: MediaQuery.of(context).size.width / 2.5,
child: DropdownButton(
hint: Text(
selectedTech,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: listStaffUsers.map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
selectedTech = val.toString();
},
);
},
),
),
The above code works perfect.
However when I want to update the job ticket to change the available staff member I want to set the initial value of the drop down menu to the staff member assigned to the job, because it isn't always guaranteed that they change the staff member allocated to the job. When I set the selected value to my initial value I am locked with that value and cannot change it.
Here is the code I am using to update the staff member.
String selectedTech = "";
int the build method I add
selectedTech = widget.staff;
Container(
// margin: EdgeInsets.only(right: 20),
width: MediaQuery.of(context).size.width / 2.5,
child: DropdownButton(
hint: Text(
selectedTech,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: listStaffUsers.map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
selectedTech = val.toString();
},
);
},
),
),
Any Guidance or examples will be greatly appreciated.

As I understand under the Widget build method you set
selectedTech = widget.staff and then return the widget like this:
Widget build(BuildContext context) {
selectedTech = widget.staff;
return Container( ...
This will systematically lock your selectedTech to widget.staff whenever the build method is called (when you call setState). I mean whenever you change the value of the dropdown, the value will not be set the actual value on the dropdown menu. Because you call setState, setState builds the widget from scratch and selectedTech = widget.staff is called in these steps.
Instead of in build method you should initialize it first, then continue to build method.
class _StaffHomeState extends State<StaffHome> {
String? selectedTech;
// Write a function to initialize the value of selectedTech
void initializeSelectedTech () {
selectedTech = widget.staff;
}
// Call this function in initState to initialize the value
#override
void initState() {
initializeSelectedTech();
super.initState();
}
// Then Widget build method
Widget build(BuildContext context) {
return Container( .....
By this way, you initialize first the value before build method and whenever state changes, the data will be persisted.
I hope it is helpful.

Related

How can I remove/disable an item in a DropdownButton's item list if it's selected on a second one (and vice-versa)?

I'm trying to make a pair of dropdown buttons that take their items from the same list, and selecting a value in one disables it in the other one, and vice versa (much like Translators software, or conversions, as this example is the second one).
I tried the onChanged methods but they only worked when checking the list AFTER changing its value (i.e, list 2's value was still showing up on list 1 until I changed list 1's value, and vice versa).
Here's the list
const List<String> tipolong = <String>['mi', 'km', 'fur', 'hm', 'ch', 'dam', 'rd', 'm', 'yd', 'ft', 'dm', 'in', 'cm', 'mm'];
And here are the two Dropdown buttons with their respective derivatives of said list
tipolong2 = List.from(tipolong);
tipolong3 = List.from(tipolong);
class _DropdownButtonState extends State<DropdownButton> {
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValueLength1,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
// This is called when the user selects an item.
setState(() {
dropdownValueLength1 = newValue!;
tipolong3 = List.from(tipolong);
tipolong3.remove(dropdownValueLength1);
});
},
items: tipolong2.map((dropdownValueLength1) {
return DropdownMenuItem<String>(
value: dropdownValueLength1,
child: Text(dropdownValueLength1),
);
}).toList(), );
}
}
class _DropdownButtonState2 extends State<DropdownButton2> {
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValueLength2,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
// This is called when the user selects an item.
setState(() {
dropdownValueLength2 = newValue!;
tipolong2 = List.from(tipolong);
tipolong2.remove(dropdownValueLength2);
});
},
items: tipolong3.map((dropdownValueLength2) {
return DropdownMenuItem<String>(
value: dropdownValueLength2,
child: Text(dropdownValueLength2),
);
}).toList(), );
}
}
How can I make it so that when I choose an item in the first list, it is removed or disabled from the second one, and vice versa? So far I've tried the onTap and onChanged methods but they both work only on my second time checking the list (the one that should disappear does so, but after I popped up the list for a second time instead of the first one).
Sorry if my explanation is not clear.
You can check that is user selected from 1st dropdown then disable 2nd dropdown and vice-a-versa
You can use IgnorePointer to achieve your functionality
1st onChanged > Item Selected > store it in var A
Check var A is not empty > Disable 2nd DropDown and same for reverse

Make a variable in build build context run once

I have a stateful widget which uses a provider to get questions. The question type looks like this:
{
"question": "What...",
"answer: 1829,
"buffer": [1928, 1874, 1825]
}
I have a shuffle method which shuffles the items passed to it. So in my widget, I have this code:
#override
Widget build(BuildContext context) {
var state = context.watch<Services>();
Tion tion;
List<int> shuffled;
int selectedNumber;
if (state.questions != null) {
tion = state.questions[0];
shuffled = shuffle([tion.answer, ...tion.buffer]); // here's my issue
}
return ...
}
Deeper in the widget tree, I render these numbers:
GridView.count(
crossAxisCount: 2,
children: List.generate(4, (index) =>
Center(
child: GestureDetector(
onTap: () => setState(() {
selectedNumber = shuffled[index]; // setstate
}),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: selectedNumber == shuffled[index] ? Color(0xff6C63FF) : Colors.grey[200],
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
child: Center(
child: Text(
'${shuffled[index]}',
style: GoogleFonts.lato(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800]
)
)
),
),
),
)
),
)
The problem is when I call setState(), the widget rebuilds, and the order of the numbers along with it. Is there any way to prevent this? I tries with initState but it's called outside the scope of context.
If you need a BuildContext for your function you can use didChangeDependencies(): It is called when a dependency of this State object changes and also immediately after initState, it is safe to use BuildContext here. Subclasses rarely override this method because the framework always calls build after a dependency changes. Some subclasses do override this method because they need to do some expensive work (e.g., network fetches) when their dependencies change, and that work would be too expensive to do for every build.
#override
void didChangeDependencies() {
// Your function.
super.didChangeDependencies();
}
Getx package also has variety of ways to insert a Middleware function. You can check them on package page.

GestureDetector and exclusive activation while calling onTap in a widget list

I'm trying to create a simple vertical scrolling calendar.
Problem is that I can't manage to find a way to reset back to previous state in case I tap on a new container.
Here's the code:
class CalendarBox extends StatelessWidget {
BoxProprieties boxProprieties = BoxProprieties();
Map item;
CalendarBox({this.item});
bool selected = false;
#override
Widget build(BuildContext context) {
return Consumer<Producer>(
builder: (context, producer, child) => GestureDetector(
onTap: () {
print(item['dateTime']);
selected = producer.selectedState(selected);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 100),
color: selected == true ? Colors.blue : Colors.grey[200],
height: 80,
width: 50,
margin: EdgeInsets.only(top: 5),
child: Column(
children: [
Text(
'${item['dayNum']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: boxProprieties.dayColor(item['dateTime'])),
),
],
),
),
),
);
}
}
Here's the situation:
One way to achieve it is, create a model for boxes and keep a value current selected block, in your model you will have the index assigned to that block,
int currentSelected =1; //initial value
class Block{
int id;
..
.. // any other stuff
}
now in your code, the check modifies to
block.id == currentSelected ? Colors.blue : Colors.grey[200],
your on tap modifies to
onTap: () {
setState(){
currentSelected = block.id
};
},
If you want to prevent the rebuild of the whole thing every time you can use valueNotifire for current selected block. Hope this gives you an idea.

Widgets get no update from the listener flutter

I have implemented an listener in the following way:
#override
void didChangeDependencies() {
final SlotDataProvider slotDevice = Provider.of<SlotDataProvider>(context);
spptask.streamdevice.listen((device) {
setState(() {
slotDevice._devices[0].name = device.name;
print("Device data received: ${device.name} ");
});
}, onError: (error) {
print("Error: $error.message");
});
super.didChangeDependencies();
}
I listen on a splitted controller and the print "Device data received:..." is called but the widget is not actualized. In the build method I do the following:
...
#override
Widget build(BuildContext context) {
final slotProvider = Provider.of<SlotDataProvider>(context);
final deviceProvider = Provider.of<DeviceDataProvider>(context);
Device slotDevice = slotProvider.getDevice(widget.slot);
Device device = deviceProvider.getDevice(widget.slot);
_dropdownMenuItems = buildDropdownMenuItems(deviceProvider.get());
return ListTile(
title: Row(
children: <Widget>[
SizedBox(
width: 140,
child: DropdownButton(
isExpanded: true,
disabledHint: Text(slotDevice.name),
hint: Text(slotDevice.name),
value: device,
items: _dropdownMenuItems,
onChanged: (value) {
device.setDevice(value);
slotDevice.setDevice(value);
}),
),
SizedBox(width: 10),
SizedBox(width: 60, child: Text('SLOT#${slotDevice.slot}')),
],
),
subtitle: Text(slotDevice.bdaddr, style: TextStyle(fontSize: 10.0)),
leading: SizedBox(
height: 40,
width: 35,
child: UsbBatteryImageAsset(slot: widget.slot),
),
trailing: Icon(Icons.keyboard_arrow_right),
);
}
}
What is missing in the above code. The SlotDataProvider is a fix list of "Device" with attributes such as name, id and so on.
#EDIT
The problem has to do with the combobox. If I change an other field, it works.
Usually for widgets to be rebuilt based on the data updated we use streambuilders
this will cause the widget to rebuild every time there is a change in the stream
it seams that your widget is being built once with the first listening of the data
have you tried wrapping the gridview in a stateful builder ?

Insert into TextField using buttons from another widget - Flutter

I have a TextField that serves as a search bar where the user can use the built in Android/iOS keyboard to type but also has the possibility to insert a special characters in the search bar from a button. In a way the typing and the other insertion is combined into one string
use case: The user types hell in the search bar then presses the button widget the search bar value becomes : hellö
I set up everything but when I click the button nothing happens (the typing from the keyboard works fine)
Here's my code:
//I have this as a global variable
TextEditingController _searchInputControllor = TextEditingController();
//This is the TextField
class _SearchBarState extends State<SearchBar> {
#override
Widget build(BuildContext context) {
return TextField(
enableInteractiveSelection: false,
controller: _searchInputControllor,
cursorColor: primaryDark,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 15.0),
border: InputBorder.none,
hintText: "Search...",
suffixIcon: Material(
color: Colors.white,
elevation: 6.0,
borderRadius: BorderRadius.all(Radius.circular(6.0),),
child: InkWell(
splashColor: Colors.greenAccent,
onTap: () {},
child: Icon(Icons.search, color: primaryDark,),
),
),
),
);
}
}
//This is the button widget
//It is supposed to add to the search bar but nothing happens
class _SpecialCharState extends State<SpecialChar> {
#override
Widget build(BuildContext context) {
return ButtonTheme(
minWidth: 40.0,
child: FlatButton(
color: Colors.transparent,
textColor: Colors.black,
padding: EdgeInsets.all(8.0),
splashColor: Colors.blue,
onPressed: () {
setState(() {
_searchInputControllor.text = _searchInputControllor.text + widget.btnVal.toLowerCase();
});
},
child: Text(
widget.btnVal
),
)
);
}
}
A. No problem at all
I think your code is working well as I tried on my Android Phone Demo.
The text field is changed as I tap the buttons.
B. Change cursor position
Nonetheless, I add this code to make the cursor automatically placed on last character.
Rather than directly changed the text, we copy its value which contains selection.
Later we offset its selection by length of newText
void appendCharacters() {
String oldText = _searchInputControllor.text;
String newText = oldText + widget.btnVal.toLowerCase();
var newValue = _searchInputControllor.value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: newText.length, //offset to Last Character
),
composing: TextRange.empty,
);
_searchInputControllor.value = newValue;
}
so we can trigger the method with code below :
Widget build(BuildContext context) {
return ButtonTheme(
minWidth: 40.0,
child: FlatButton(
onPressed: appendCharacters, // call a function
),
);
}
Working App Repository
You may look into this repo and build yourself. Github