Flutter - calling setState() before the build - flutter

I use a futureBuilder to display date inside TextFormFields, if there is data in the webservice I call in the futureBuilder for the date I selected in the DateTimePicker, the TextFormField is disabled and the data is displayed in it. Else, the textFormField is enabled.
I also have a button that I want to disable if there is data received and enable if there isn't, so I used a boolean.
Here is my code :
child: FutureBuilder<double?>(
future: getTimes(selectedDate),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
_timeController.clear();
setState(() {
_isButtonDisabled = false;
});
return TextFormField(
controller: _timeController,
textAlign: TextAlign.center,
enabled: false,
decoration: InputDecoration(
hintText: snapshot.data.toString() + " h",
contentPadding: EdgeInsets.zero,
filled: true,
fillColor: Colors.white70
),
);
}
else {
setState(() {
_isButtonDisabled = true;
});
return TextFormField(
controller: _timeController,
textAlign: TextAlign.center,
enabled: true,
decoration: InputDecoration(
hintText: "0 h",
contentPadding: EdgeInsets.zero,
filled: true,
fillColor: Colors.white
),
);
}
}
)
This was causing me the error setState() or markNeedsBuild called during build , so thanks to the answers of this topic I encapsulated the setState method in WidgetsBinding.instance.addPostFrameCallback((_)
Here is what my code looks like now :
child: FutureBuilder<double?>(
future: getTimes(selectedDate),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
_timeController.clear();
WidgetsBinding.instance?.addPostFrameCallback((_){
setState(() {
_isButtonDisabled = false;
});
});
return TextFormField(
controller: _timeController,
textAlign: TextAlign.center,
enabled: false,
decoration: InputDecoration(
hintText: snapshot.data.toString() + " h",
contentPadding: EdgeInsets.zero,
filled: true,
fillColor: Colors.white70
),
);
}
else {
WidgetsBinding.instance?.addPostFrameCallback((_){
setState(() {
_isButtonDisabled = true;
});
});
return TextFormField(
controller: _timeController,
textAlign: TextAlign.center,
enabled: true,
decoration: InputDecoration(
hintText: "0 h",
contentPadding: EdgeInsets.zero,
filled: true,
fillColor: Colors.white
),
);
}
}
)
The problem that I have now is my TextFormFields aren't clickable anymore, and the button is always enabled, may be a misused / misunderstood the addPostFrameCallback function.
Thanks for helping,

You have DateTimePicker, after the selecting date-time you can call the future.
getTimes() returns nullable double. Before retuning data, compare value is null or not and set _isButtonDisabled based on it, assign true/false.
bool _isButtonDisabled = true; // set the intial/watting state you want
Future<double?> getTimes(DateTime time) async {
//heavy operations
return await Future.delayed(Duration(seconds: 3), () {
return 4; //check with null +value
});
}
----
#override
Widget build(BuildContext context) {
print("rebuild");
return Column(
children: [
ElevatedButton(
onPressed: () async {
final selectedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now().subtract(Duration(days: 4444)),
lastDate: DateTime.now().add(Duration(days: 4444)),
);
if (selectedDate == null) return;
final response = await getTimes(selectedDate);
print(response);
setState(() {
_isButtonDisabled = response != null;
});
},
child: Text("Select Date"),
),
ElevatedButton(
onPressed: _isButtonDisabled ? null : () {}, child: Text("t"))
],
);}

Related

RawAutoComplete initialValue not setting in Flutter

I was able to get initialValue to work in AutoComplete. But there is a bug where the drop down goes off screen. So I found a workaround on slack and I am not using RawAutoComplete and trying to get the initial Value to work. I tried to set it in RawAutoComplete with:
child: RawAutocomplete<String>(
initialValue: TextEditingValue(text: itemTypeController.text),
When I look at the documentation I see:
This parameter is ignored if [textEditingController] is defined
But I am not sure how to set it otherwise.
I initially tried to set it in the TextFormField like so:
child: TextFormField(
controller: itemTypeController,
initialValue: "test",
focusNode: focusNode,
onEditingComplete: onEditingComplete,
decoration: const InputDecoration(
labelText: "Item type*",
hintText: 'What is the item?',
),
),
But that throws this error:
'initialValue == null || controller == null': is not true.
Which I assume is because if controller is present it woudl take the initial value from there. If both are not null then it doesnt know what to pic. I need the controller because I need to retrieve the value in the form to submit to my database.
Full code below:
LayoutBuilder(
builder: (context, constraints) => InputDecorator(
decoration: const InputDecoration(
icon: Icon(Icons.style),
border: InputBorder.none,
),
child: RawAutocomplete<String>(
initialValue: TextEditingValue(text: itemTypeController.text),
// first property
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return itemTypeList;
}
return itemTypeList.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
//second property where you can limit the overlay pop up suggestion
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
height: 200.0,
// set width based on you need
width: constraints.biggest.width * 0.8,
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final String option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
// third property
fieldViewBuilder:
(context, controller, focusNode, onEditingComplete) {
itemTypeController = controller;
return Focus(
onFocusChange: (hasFocus) {
if (temperatureItemTypes
.contains(itemTypeController.text.trim())) {
//show temperature field
setState(() {
temperatureField = true;
});
} else {
setState(() {
temperatureField = false;
});
}
if (volumeItemTypes
.contains(itemTypeController.text.trim())) {
//show temperature field
setState(() {
volumeField = true;
});
} else {
setState(() {
volumeField = false;
});
}
},
child: TextFormField(
controller: itemTypeController,
focusNode: focusNode,
onEditingComplete: onEditingComplete,
decoration: const InputDecoration(
labelText: "Item type*",
hintText: 'What is the item?',
),
),
);
}),
),
);

How to update state with each change on text field flutter

I am building a search bar which brings some resuts from algolia. everything is working fine, except that the results listview view doesn't show the new resuts until I close the keyboard. but I need to update automatically with every new letter I write in the text field (while the keyboard is opened), same like auto complete function. what is mimssing here?
(note that all of this is inside a buttomsheet)
I also tried to replace the controller listner with onChange (){}, same issue is there.
the list view doesn't rebuild untill I close the keyboard.
The Funcion and the listner Code:
class _CategoriesPageState extends State<CategoriesPage> {
String _searchTerm = "";
List<AlgoliaObjectSnapshot> _results = [];
bool _searching = false;
TextEditingController _searchText = TextEditingController(text: "");
_search() async {
setState(() {
_searching = true;
});
Algolia algolia = const Algolia.init(
applicationId: 'XP6QXPHMDJ',
apiKey: '283351eb9d0a111a8fb4f2fdb7b8450a',
);
AlgoliaQuery query = algolia.instance.index('BusinessProfilesCollection');
query = query.query(_searchText.text);
_results = (await query.getObjects()).hits;
setState(() {
_searching = false;
});
}
#override
void initState() {
_searchText.addListener(() {
setState(() {
_search();
});
});
super.initState();
}
the Text Field Code:
TextField(
controller: _searchText,
style: GoogleFonts.lato(
fontStyle: FontStyle.normal,
color: Colors.grey[850],
fontSize: 14.sp,
),
decoration: const InputDecoration(
border: InputBorder.none,
hintText: 'Search ...',
hintStyle:
TextStyle(color: Colors.black),
)),
The Results Widget:
Container(
height: 300.h,
child: _searching == true
? Center(
child: Text("Searching, please wait..."),
)
: _results.length == 0
? Center(
child: Text("No results found."),
)
: ListView.builder(
itemCount: _results.length,
itemBuilder:
(BuildContext ctx, int index) {return ...}))
A bottom sheet doesnt update state by default. Wrap the content of the bottom sheet with a statefulbuilder
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
child: TextField(onChanged: (val) {
setState(() {
//This will rebuild the bottom sheet
});
}),
);
});
});
TextField(
onChanged: (value) {
setState(() {
//This will refresh the page
});
},
controller: _searchText,
style: GoogleFonts.lato(
fontStyle: FontStyle.normal,
color: Colors.grey[850],
fontSize: 14.sp,
),
decoration: const InputDecoration(
border: InputBorder.none,
hintText: 'Search ...',
hintStyle:
TextStyle(color: Colors.black),
),
),

Flutter - How to reset Autocomplete list after fetching data from server?

I have Autocomplete list:
List<CompanyName> companyNames = <CompanyName>[
const CompanyName(name: 'No Data'),
];
And this works, only one item No Data is on the array, but that array is filled by data from the server, and the problem is when you press autocomplete on start you will see the No Data item on the list, after server fetching data, the list will not be updated by data from the server.
My idea is to create a local variable that will be updated by the async call, and that variable should hide autocomplete list before the server responds, or refresh the (re-render) widget after fetching...
Autocomplete<CompanyName>(
optionsBuilder:
(TextEditingValue textEditingValue) {
return companyNames.where((CompanyName companyName) {
return companyName.name.toLowerCase().contains(textEditingValue.text.toLowerCase());
}).toList();
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<CompanyName>
onSelected,
Iterable<CompanyName> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
child: ConstrainedBox(constraints: const BoxConstraints(maxHeight: 280,),
child: SizedBox(width: 280,
height: companyNames.length <= 1 ? 80 : 280,
child: ListView.builder(padding: const EdgeInsets.all(10.0),
itemCount: options.length, itemBuilder: (BuildContext context, int index) { final CompanyName option = options.elementAt(index);
return GestureDetector(
onTap: () { onSelected(option); },
child: ListTile( title: Text(option.name, style: TextStyle(color: isDarkMode ? Colors.white : Colors.black)),
),
);
})))));
},
fieldViewBuilder:
(context,
controller,
focusNode,
onEditingComplete) {
return TextFormField(
controller:
controller,
focusNode:
focusNode,
onEditingComplete:
onEditingComplete,
keyboardType:
TextInputType
.text,
autocorrect:
false,
decoration: InputDecoration(
isDense: true,
hintText: "Company Name",
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(10.0),
),
fillColor: isDarkMode ? const Color(0XFF212124) : Colors.white,
filled: true),
validator: (value) {
if (value ==
null ||
value.isEmpty) {
return 'Please enter company name';
} else {
setState(
() {
client =
value;
});
}
return null;
});
},
onSelected:
(CompanyName
selection) {
setState(() {
brokerCompany =
selection
.name;
});
},
displayStringForOption:
(CompanyName
option) =>
option
.name,
),
What is the best option and where is the best option to put the variable and re-render Autocomplete()?

hide the texfield cursor in flutter

I am getting one issue. I want to hide the cursor after clicking sign in button
I tried with FocusNode but it is not working .I have searched in google but I didn't get the answer.Actually what is the way to do this ? Here is my code.
Widget _buildUserIdField() {
return Observer(
builder: (context) {
return TextFieldWidget(
hint: AppTranslations.of(context).text("mobile_number"),
inputType: TextInputType.text,
textController: _userEmailController,
inputAction: TextInputAction.next,
fillColor: AppColors.gray[300],
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_passwordFocusNode);
},
errorText: _store.formErrorStore.userEmail,
);
},
);
}
Widget _buildPasswordField() {
return Observer(
builder: (context) {
return TextFieldWidget(
hint: AppTranslations.of(context).text("enter_password"),
isObscure: true,
padding: EdgeInsets.only(top: 16.0),
icon: Icons.lock,
fillColor: AppColors.gray[300],
iconColor: Colors.black54,
textController: _passwordController,
focusNode: _passwordFocusNode,
errorText: _store.formErrorStore.password,
onFieldSubmitted: (value){
FocusScope.of(context).dispose();
}
);
},
);
}
Widget _buildSignInButton() {
return RoundedButtonWidget(
buttonText: AppTranslations.of(context).text("login"),
buttonColor: AppColors.blue[500],
textColor: Colors.white,
onPressed: () async {
FocusScope.of(context).dispose();
if (_userEmailController.text.isNotEmpty &&
_passwordController.text.isNotEmpty) {
_login();
} else {
AlertError.showErrorMessage(context, AppTranslations.of(context).text("fill_all_fields"));
}
},
);
}
cursorColor: Colors.transparent
cursorWidth: 0
Use this:
FocusScope.of(context).unfocus();

Flutter how to display datepicker when textformfield is clicked

new TextFormField(
decoration: new InputDecoration(hintText: 'DOB'),
maxLength: 10,
validator: validateDob,
onSaved: (String val) {
strDob = val;
},
),
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2016),
lastDate: new DateTime(2019)
);
if(picked != null) setState(() => _value = picked.toString());
}
I created one textFormField when i click the field i want to display datepicker then i have to select one date from the picker after selecting the date i want to set the selected date in the textFormField.
Update 2020:
As pointed by another answer #Lekr0 this can now be done using onTap() property of TextFormField.
TextFormField(
onTap: (){
// Below line stops keyboard from appearing
FocusScope.of(context).requestFocus(new FocusNode());
// Show Date Picker Here
},
)
Original Answer:
Simple Way of Doing it :
Wrap your TextFormField with IgnorePointer & wrap IgnorePointer with InkWell
InkWell(
onTap: () {
_selectDate(); // Call Function that has showDatePicker()
},
child: IgnorePointer(
child: new TextFormField(
decoration: new InputDecoration(hintText: 'DOB'),
maxLength: 10,
// validator: validateDob,
onSaved: (String val) {},
),
),
),
Also in Your _selectDate() make lastDate: new DateTime(2020)); else you will get error.
TextEditingController dateCtl = TextEditingController();
TextFormField(
controller: dateCtl,
decoration: InputDecoration(
labelText: "Date of birth",
hintText: "Ex. Insert your dob",),
onTap: () async{
DateTime date = DateTime(1900);
FocusScope.of(context).requestFocus(new FocusNode());
date = await showDatePicker(
context: context,
initialDate:DateTime.now(),
firstDate:DateTime(1900),
lastDate: DateTime(2100));
dateCtl.text = date.toIso8601String();},)
You can use OnTap property to achieve this
TextFormField(
onTap: (){
// Below line stops keyboard from appearing
FocusScope.of(context).requestFocus(new FocusNode());
// Show Date Picker Here
},
)
To stop keyboard from appearing, you can set the readOnly property of the TextFormField to true.
TextFormField(
readOnly: true,
...
);
TextEditingController intialdateval = TextEditingController();
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2020),
lastDate: new DateTime(2030));
if (picked != null)
setState(
() => { data.registrationdate = picked.toString(),
intialdateval.text = picked.toString()
}
);
}
TextFormField(
// focusNode: _focusNode,
keyboardType: TextInputType.phone,
autocorrect: false,
controller: intialdateval,
onSaved: (value) {
data.registrationdate = value;
},
onTap: () {
_selectDate();
FocusScope.of(context).requestFocus(new FocusNode());
},
maxLines: 1,
//initialValue: 'Aseem Wangoo',
validator: (value) {
if (value.isEmpty || value.length < 1) {
return 'Choose Date';
}
},
decoration: InputDecoration(
labelText: 'Registration Date.',
//filled: true,
icon: const Icon(Icons.calendar_today),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid),
),
),
I don't know why you want to display DatePicker on click of TextFormField?
BTW you have to set enabled=false property of TextFormField and warp TextFormField to GestureDetector that has onTap property where you can call your DatePicker Method.
final _dateController = useTextEditingController();
TextFormField(
readOnly: true,
controller: _dateController,
decoration: InputDecoration(
labelText: 'Date',
),
onTap: () async {
await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2015),
lastDate: DateTime(2025),
).then((selectedDate) {
if (selectedDate != null) {
_dateController.text =
DateFormat('yyyy-MM-dd').format(selectedDate);
}
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter date.';
}
return null;
},
)
Try this!
GestureDetector(
onTap: () {
var tm = showCupertinoDatePicker(
firstDate:
defaultDateTime.add(Duration(days: -1)),
lastDate: defaultDateTime.add(Duration(days: 1)),
context: context,
initialDate: newDate);
tm.then((selectedDate) {
if (selectedDate != null) {
setState(() {
newDate = selectedDate;
});
dateController.text =
"${DateTimeUtils.formatDate(newDate)}";
}
});
},
child: AbsorbPointer(
child: TextFormField(
controller: dateController,
autofocus: false,
validator: ((val) {
if (val.trim().isEmpty) {
return “Select Date";
}
return null;
}),
decoration: InputDecoration(
icon: Icon(Icons.today),
suffix: Text("Tap to change",
style: Theme.of(context)
.textTheme
.caption))),
),
),
I'm using all place comman widget