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,
);
}),
],
),
Related
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.
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?',
),
),
);
}),
),
);
I'm facing something strange. I have textfield in my app that can be cleared without problem, and once cleared, the delete icon disappears.
But, when i want to clear a textfield that is in a AlertDialog, the text is cleared, but the icon to delete stays on.
final TextEditingController _namespaceController = TextEditingController();
void clearNamespaceController() {
_namespaceController.clear();
setState(() {});
}
Widget _displayDialogForEdition(result, index, context) {
return IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() {
_namespaceController.text = result[index].name;
});
showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text("Modification d'une configuration"),
content: Container(
// padding: EdgeInsets.all(16),
child: TextField(
controller: _namespaceController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Theme.of(context).primaryColor),
border: OutlineInputBorder(),
labelText: 'Exclure le Namespace',
suffixIcon: _namespaceController.text.length == 0
? null
: IconButton(
icon: Icon(Icons.clear),
onPressed: clearNamespaceController,
),
labelStyle: Theme.of(context).textTheme.headline6),
),
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
});
}
You need to use StatefulBuilder() to use setState() inside AlertBox(). Using StatefulBuilder() it build the UI of only your AlertBox(). If you don't use StatefulBuilder() the UI of your AlertBox() wont update if you check your _controller.text==0 and change your Icon.
You can use like this.
showDialog(
context: context,
builder: (ctx) {
bool isTextClear = true;
return StatefulBuilder(builder: (ctx, setState) {
return AlertDialog(
title: Text("Add name"),
content: Container(
child: TextField(
controller: _controller,
onChanged: (val) {
setState(
() {
if (_controller.text.isEmpty) {
isTextClear = true;
} else {
isTextClear = false;
}
},
);
},
decoration: InputDecoration(
labelText: "Name",
prefixIcon: Icon(Icons.search),
suffixIcon: isTextClear
? null
: IconButton(
onPressed: () {
setState(() {
isTextClear = true;
_controller.clear();
});
},
icon: Icon(
Icons.clear,
),
),
),
),
),
);
});
},
);
You can try here
That's because your TextField holds the Focus you need to unfocus that in order to get what you want.
I have a DropdownButtonFormField where the last item is a DropdownMenuItem to add a new Object using a Dialog.
Padding(
padding: const EdgeInsets.only(bottom: 15),
child: Observer(
builder: (_){
return DropdownButtonFormField(
value: createdContentStore.subjectTitleSelected,
isDense: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
isDense: true,
border: OutlineInputBorder()
),
onChanged: (value) async {
// print(value);
if(value == 'newSubject'){
Subject newSubject = await showDialog(
context: context,
builder: (_) => CreatedSubjectDialogBox(isNewContent: true,)
);
if(newSubject != null){
createdContentStore.setSubjectTitleSelected(newSubject.title);
createdContentStore.setSubject(newSubject);
} else {
// WHAT CAN I DO HERE TO RESET DROP'S VALUE?
}
} else {
createdContentStore.setSubjectTitleSelected(value);
}
},
iconSize: 30,
hint: Text('Selecione uma matéria'),
items: subjectStore.subjectList.map((subject) => DropdownMenuItem(
value: subject.title,
child: Text(subject.title),
onTap: () {
createdContentStore.setSubject(subject);
},
)).toList()..add(DropdownMenuItem(
value: 'newSubject',
child: Center(
child: Text(
'Nova Matéria'.toUpperCase(),
style: TextStyle(color: redRevise),
),
),
)),
);
},
),
);
When the Dialog is shown the user can create a new Object that will appear in the Dropdown. When the user cancels the Dialog it is showing the last item. The desired behavior is to show the hint instead.
Can someone help me?
Thank you!
All you have to do is remove the value from the drop down,
DropdownButtonFormField(
//** REMOVE THE VALUE **
isDense: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
isDense: true,
border: OutlineInputBorder()
),
onChanged: (value) async {
if(value == 'newSubject'){
Subject newSubject = await showDialog(
context: context,
builder: (_) => CreatedSubjectDialogBox(isNewContent: true,)
);
if(newSubject != null){
createdContentStore.setSubjectTitleSelected(newSubject.title);
createdContentStore.setSubject(newSubject);
} else {
// WHAT CAN I DO HERE TO RESET DROP'S VALUE?
}
} else {
createdContentStore.setSubjectTitleSelected(value);
}
},
iconSize: 30,
hint: Text('Selecione uma matéria'),
items: subjectStore.subjectList.map((subject) => DropdownMenuItem(
value: subject.title,
child: Text(subject.title),
onTap: () {
createdContentStore.setSubject(subject);
},
)).toList()..add(DropdownMenuItem(
value: 'newSubject',
child: Center(
child: Text(
'Nova Matéria'.toUpperCase(),
style: TextStyle(color: redRevise),
),
),
)),
);
},
),
);
I just need to disable the action button while the search query is empty. I'm not sure if this is possible with native Flutter.
Does this need to be done with each platform specifically? (iOS / Android)
You have a couple of options.
You can create a new focus:
FocusScope.of(context).requestFocus(new FocusNode())
Example:
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: ....,
)
)
)
}
The other option is to release the existing focus:
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
There is also a package: keyboard_dismisser
You just need to add a FocusNode to your TextFormField and request focus for it if the user presses the submit button on the keyboard when the field is empty. Here is a complete example:
class KeyboardKeeper60943209 extends StatefulWidget {
#override
_KeyboardKeeper60943209State createState() => _KeyboardKeeper60943209State();
}
class _KeyboardKeeper60943209State extends State<KeyboardKeeper60943209> {
List<String> items = List.generate(20, (index) => 'item $index');
TextEditingController _textEditingController = TextEditingController();
FocusNode _focusNode = FocusNode();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextFormField(
focusNode: _focusNode,
controller: _textEditingController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
labelText: 'Search',
hasFloatingPlaceholder: false,
),
// This is the key part
onFieldSubmitted: (value) {
if(value == ''){
_focusNode.requestFocus();
}
},
),
),
FlatButton(onPressed: search, child: Text('Search'))
],
),
Expanded(
child: Container(
child: ListView.builder(
itemBuilder: (context, index){
return ListTile(
title: Text(items[index]),
subtitle: Text(items[index]),
);
}
),
),
),
],
);
}
void search(){
print('search');
}
}
TextFormField(
readOnly: true,
showCursor: false,
controller: consName,
decoration: InputDecoration(
hintText: 'Enter Name',
labelText: 'Username',
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.deepPurple,
width: 2,
),
),
),
),
Very Simple & Easy Use "readOnly: true" for disabling keyboard and if you don't want pointer or cursor then "showCursor: false". That's it, hope this'll work. Eat Sleep Workout Code Repeat. Happy Coding😊.
You can simply use textInputAction: TextInputAction.none on the TextField.