RawAutoComplete initialValue not setting in Flutter - 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?',
),
),
);
}),
),
);

Related

Flutter AutoComplete Widget, not letting me choose text outside of an Array

My code is as follows:
final TextEditingController _locationController = TextEditingController();
late TextEditingController myController;
late List<String> locationSuggestions; // Populated with options in code first
Autocomplete<String>(
initialValue: TextEditingValue(text:_locationController.text),
optionsMaxHeight: 50,
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text.isEmpty) return const Iterable<String>.empty();
return locationSuggestions.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
optionsViewBuilder: (context, onSelected, options){
return Material(
elevation: 8,
child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 0.0),
itemBuilder: (BuildContext context, int index) {
final String option = options.elementAt(index);
return ListTile(
dense: true,
// tileColor: Colors.deepOrange,
title: SubstringHighlight(
text: option,
term: myController.text,
textStyleHighlight: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16.0),
),
onTap: () => onSelected(option),
);
},
separatorBuilder: (context, index) => const Divider(),
itemCount: options.length,
));
},
onSelected: (String value)=> myController.text = value,
fieldViewBuilder: (context, controller, focusNode, onEditingComplete){
myController = controller;
return TextFormField(
controller: myController,
focusNode: focusNode,
maxLines: 1,
maxLength: 30,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: 'Location',
prefixIcon: Icon(
Icons.add_location,
size: 35.0,
color: Theme.of(context).colorScheme.primary,
)),
onEditingComplete: onEditingComplete,
//onTap: ()=>FocusManager.instance.primaryFocus?.unfocus(),
);
},
),
The Problem:
When I go and type the text, I see options appearing below, if I choose one of the options, all goes well and good.
BUT... I am not able to type a new 'similar' custom option. So for example, if I get a suggestion for the word 'Apple' and I just want to stop typing at App, I can't do it. The done button on the keyboard does not hide the keyboard and if I try to move away from the field the entire 'Apple' appears in the text field. I tried experimenting with unfocus but it is not the right way and I am sure I am missing a feature here.

Flutter show Cancel-Button on CupertinoSearchTextField

I am using the CupertinoSearchTextField in my app. It is working fine so far but I am missing one feature: the Cancel-Button.
In native iOS you can set to show the button which looks like this:
Does Flutter provide this functionality? I couldn't find it anywhere.
Clarification:
I don't mean the x/clear-button. I know that is build-in. What I mean is the actual Cancel-button which removes focus from the textField.
use Typeahead package.then in suffixIcon, you can add cancel feature to clear field.
TypeAheadField<String>(
hideOnEmpty: true,
minCharsForSuggestions: 2,
getImmediateSuggestions: true,
textFieldConfiguration: TextFieldConfiguration(
controller: cont_search,
cursorColor: Colors.grey,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
//here the cancel button
suffixIcon: IconButton(
padding: EdgeInsets.fromLTRB(8, 4, 8, 8),
icon: Icon(Icons.clear),
onPressed: (){
cont_search.clear();
},
),
focusColor: Colors.black,
focusedBorder: InputBorder.none,
border: InputBorder.none,
//hintText: 'What are you looking for?',
icon: Icon(Icons.search),
),
onSubmitted: (value){
print("value taken is ${value}");
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => search_page(value)
));
}
),
suggestionsCallback: (String pattern) async {
return matches
.where((item) =>
item.toLowerCase().startsWith(pattern.toLowerCase()))
.toList();
},
itemBuilder: (context, String suggestion) {
return ListTile(
title: Text(suggestion),
);
},
onSuggestionSelected: (String suggestion) {
//push to page
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => search_page(suggestion)
));
print("Suggestion selected ${suggestion}");
},
)
If you wanna override x/clear-button's behaviour to unfocus the textfield, use this. Otherwise, you can put search textfield and a clear button in a row and implement button's behaviour like this. Problem solved.
onSuffixTap: (){
FocusScope.of(context).unfocus();
}
I ended up building it myself. I made use of the Focus-Widget and most important the AnimatedPadding. My code looks like this:
Row(
children: [
Flexible(
child: AnimatedPadding(
duration: const Duration(milliseconds: 100),
padding: EdgeInsets.only(right: _isSearching ? 50 : 0),
child: Focus(
onFocusChange: (hasFocus) {
setState(() {
_isSearching = hasFocus;
});
},
child: CupertinoSearchTextField(
placeholder: 'Suche'.tr,
controller: _textEditingController,
focusNode: _focusNode,
),
),
),
),
if (_isSearching)
Tappable(onTap: () {
dismissKeyboard();
}, builder: (context, isTapped) {
return AnimatedText(
text: 'Abbrechen',
isTapped: isTapped,
style: AppTextStyles.avenirNextH4Regular,
color: grey,
);
}),
],
),

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()?

Flutter - calling setState() before the build

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"))
],
);}

how can i solve blocProvider context problem?

I have submit button, but when i press the button it gives this error:
BlocProvider.of() called with a context that does not contain a
CreatePostCubit.
No ancestor could be found starting from the
context that was passed to BlocProvider.of(). This
can happen if the context you used comes from a widget above the
BlocProvider.
The context used was: Builder
I suppose contexts get mixed. how can i solve this error?
my code:
Widget createPostButton(BuildContext context) {
final TextEditingController _postTitleController = TextEditingController();
final TextEditingController _postDetailsController = TextEditingController();
final TextEditingController _priceController = TextEditingController();
final _formKey = GlobalKey<FormState>(debugLabel: '_formKey');
return BlocProvider<CreatePostCubit>(
create: (context) => CreatePostCubit(),
child: Padding(
padding: const EdgeInsets.only(right: 13.0, bottom: 13.0),
child: FloatingActionButton(
child: FaIcon(FontAwesomeIcons.plus),
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
autocorrect: true,
controller: _postTitleController,
textCapitalization: TextCapitalization.words,
enableSuggestions: false,
validator: (value) {
if (value.isEmpty || value.length <= 4) {
return 'Please enter at least 4 characters';
} else {
return null;
}
},
decoration:
InputDecoration(labelText: 'Post Title'),
)),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
controller: _postDetailsController,
autocorrect: true,
textCapitalization: TextCapitalization.words,
enableSuggestions: false,
validator: (value) {
if (value.isEmpty || value.length <= 25) {
return 'Please enter at least 25 characters';
} else {
return null;
}
},
decoration: InputDecoration(
labelText: 'Write a post details'),
)),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
controller: _priceController,
enableSuggestions: false,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
validator: (value) {
if (value.isEmpty || value.length >= 4) {
return 'Please enter a valid value';
} else {
return null;
}
},
decoration:
InputDecoration(labelText: 'Enter the Price'),
)),
OutlinedButton(
style: OutlinedButton.styleFrom(
primary: Colors.white,
backgroundColor: Colors.blue,
),
child: Text("Submit"),
onPressed: () => {
BlocProvider.of<CreatePostCubit>(context)
.createNewPost(
postTitle: _postTitleController.text,
postDetails: _postDetailsController.text,
price: _priceController.text)
},
),
],
),
),
),
);
});
},
),
),
);
}
Your showDialog builder is using new context. The widget returned by the builder does not share a context with the location that showDialog is originally called from.
Just rename builder parameter into something else
showDialog(
context: context,
barrierDismissible: false,
builder: (dialog_context) { // instead of context
return AlertDialog(
....
Also you need to wrap BlocProvide child with builder(so it can access inherited BlocProvider)
BlocProvider<CreatePostCubit>(
create: (context) => CreatePostCubit(),
child: Builder(
builder: (BuildContext context) => Padding(
...
Use BlocProvider.of<CreatePostCubit>(context,listen:false) instead of BlocProvider.of<CreatePostCubit>(context) in you submit button.
I think the problem is that context in
BlocProvider.of<CreatePostCubit>(context) refer to the orginal context which doesn't have cubit so try to rename the context into sth else like _context :
create: (_context) => CreatePostCubit(),
and when calling like this
BlocProvider.of<CreatePostCubit>(_context)