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

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.

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?',
),
),
);
}),
),
);

State of the dialog not updating on dropdown change

So I have this function to show a reusable dialog that have a list of widget. The main problem lies in the dropdown and the text field. to begin with my dropdown value is A and I wanted the text field to only show when the dropdown value is B.
I tried some code already but when I choose the value B the text field won't show up, then if I close the dialog and try to open it again next time, that will show the text field that I want, but the value in the drop down is A not value B.
How can I achieve something like what I wanted ?
Here's my code :
List<String> itemList = ["notMyExpertise", "Alasan lainnya"];
String selectedReason = '';
void rejectDialog(
{required dynamic ticketData,
required String ticketId,
required VoidCallback onDeclineConfirmed}) {
showDialog(
context: context,
builder: (context) {
return ReusableConfirmationDialog(
titleText: 'hello'.tr(),
contentText: 'hello bro let me try to help you first before '.tr(),
confirmButtonText: 'sure'.tr(),
declineButtonText: 'cancel'.tr(),
onDecline: () {
Navigator.pop(context);
},
onConfirm: onDeclineConfirmed,
additionalWidget: Column(
children: [
ReusableDropdownButton(
itemList: itemList,
width: 197,
height: 26,
onChanged: (newValue) {
setState(() {
selectedReason = newValue;
});
},
),
const SizedBox(height: 10),
if (selectedReason == 'Alasan lainnya')
Container(
constraints: const BoxConstraints(minHeight: 78),
width: 230,
decoration: BoxDecoration(
color: othersChatColor,
borderRadius: BorderRadius.circular(15),
),
padding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
child: TextFormField(
controller: ticketTextController,
maxLength: 100,
inputFormatters: [
LengthLimitingTextInputFormatter(1000),
],
style: primaryColor400Style.copyWith(
fontSize: fontSize11,
),
maxLines: null,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
hintText: 'reasonHint'.tr(),
hintStyle: weight400Style.copyWith(
color: hintColor, fontSize: fontSize11),
counterText: '',
),
),
),
const SizedBox(height: 5),
],
),
);
});
}
Because the dialog and bottom sheets don't have states so you must create a different stateful widget for the widget you want to show in the dialog or sheet and then return it from the dialog or bottom sheet. that's how it will work as a stateful widget and will update as you want.
void rejectDialog(
{required dynamic ticketData,
required String ticketId,
required VoidCallback onDeclineConfirmed}) {
showDialog(
context: context,
builder: (context) {
return StateFulWidgetYouCreate();
});
}

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

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)