Flutter TextField calls onSubmitted unexpectedly - flutter

My case is I have a widget with TextField used for search. When I type something in TextField the cross icon become visible in suffixIcon (to clear). I do not do search and just click the cross icon to clear the entered input but onSubmitted is called and search executed!!! But I don't need it! I do not submit the text input, I cancel it!!
final searchClear = ValueNotifier(false);
final searchController = TextEditingController();
// in initState method:
searchController.addListener(() {
searchClear.value = searchController.text.isNotEmpty;
});
// in build method:
TextField(
...
controller: searchController,
suffixIcon: ValueListenableBuilder<bool>(
valueListenable: searchClear,
builder: (_,visible,child) {
return Visibility(
visible: visible,
child:child,
);
},
child: InkWell(
child: Icon(Icons.close),
onTap: () {
searchController.clear();
searchFocus.unfocus();
}
),
),
onSubmitted: (value) {
if(value.isEmpty) {
FocusScope.of(context).requestFocus(searchFocus);
} else {
widget.search(value);
}
}
),
P.S. Any ideas to work around this?

Related

Why is my GestureDetector onTap event not firing?

I've created a custom form field and in it is a GestureDetector but the onTap is not firing. I want to use the GestureDetector to collect the click event when someone clicks on the form field in a disabled state so I can launch a dialog box. Code is below. I'm wondering what I must be doing wrong or misunderstanding.
Widget _customFormField(
{required String title,
required String initialValue,
required int maxLines,
required int maxLength,
required bool enabled,
required TextEditingController controller,
bool autoFocus = false,
required FocusNode currentFocusNode,
required FocusNode futureFocusNode,
Function? function,
bool formEnd = false}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
fontFamily: 'Ariel',
color: Colors.pink)),
GestureDetector(
onTap: () {
print('onTap'); // <-------------------- This never gets hit
function;
},
child: TextFormField(
autofocus: autoFocus,
enabled: enabled,
textInputAction:
formEnd ? TextInputAction.done : TextInputAction.next,
onEditingComplete: () =>
FocusScope.of(context).requestFocus(futureFocusNode),
focusNode: currentFocusNode,
controller: controller,
keyboardType: TextInputType.text,
validator: (value) {
if (value!.isEmpty) {
if (_formValid) {
_formValid = false;
currentFocusNode.requestFocus();
}
//title.toLowerCase()
return 'Please enter the ${title.toLowerCase()}';
} else {
return null;
}
},
),
),
],
);
}
I've tried changing it to an InkWell but it behaves the same.
I've removed some styling code from the example but nothing that could have an affect on the problem.
well the function property that you got from the properties are the definition of the method, when you do :
onTap: () {
print('onTap');
function;
},
you not calling the method, you putting it there, you need to call it so it runs, by adding () or by calling call() on it.
onTap: () {
print('onTap');
function(); // like this
// function.call(); or like this
},
Try the following:
onTap: () {
print('onTap'); // <-------------------- This never gets hit
if (function != null) function.call();
}

How to remove ElevatedButton?

I have a question, how do I completely remove the button from the screen? I know that it is possible to disable it using Null, but this does not suit me, because it will still be displayed, albeit in the off state. I would like the button to be completely removed after a few clicks on it, how can I do this?
ElevatedButton(
onPressed: () {
setState(() {
_clickBloc.add(ClickUpdate());
});
},
),
You can wrap ElevatedButton with Visible Widget. And make Visible widget property to false after few clicks.
bool visibleVar = true;
Visibility(
child: ElevatedButton(
onPressed: () {
setState(() {
_clickBloc.add(ClickUpdate());
visibleVar();
});
},
),
visible: visibleVar,
),
void changeVisibility(){
visibleVar = ! visibleVar;
}
You can also use conditional if with a bool like bool showThisWidget = true
if (showThisWidget) ElevatedButton(....)
If it is on child , most child accept null.
child: showThisWidget? ElevatedButton(....) :null
You can use the Visibility Widget to remove the button from the screen like:
bool visible = true;
void makeItUnvisible() {
setState(() {
visible = false;
});
}
Visibility(
visible: visible,
child: ElevatedButton(
onPressed: makeItUnvisible,
child: const Text('Button Text'),
),
),

onEditingComplete is not called after unfocus

I have a TextField like this. The additional code is necessary to show that in different situations, I do various focus manipulation.
final node = FocusScope.of(context);
Function cleanInput = () => {controller.text = controller.text.trim()};
Function onEditingComplete;
Function onSubmitted
TextInputAction textInputAction;
if (!isLast) {
onEditingComplete = () => {
cleanInput(),
node.nextFocus(),
};
onSubmitted = (_) => {cleanInput()};
textInputAction = TextInputAction.next;
} else {
onEditingComplete = () => {
cleanInput(),
};
onSubmitted = (_) => {
cleanInput(),
node.unfocus(),
};
textInputAction = TextInputAction.done;
}
Widget textInput = TextField(
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
As you can see, I have functions I want to run onEditingComplete. However, this only gets called when I press the Next or Done buttons on my keyboard (or the Enter key in an emulator). If I change focus by tapping on a different field, this function does not get called.
I have tried using a Focus or FocusNode to help with this, but when I do so, the onEditingComplete function itself no longer works.
How can I get the desired effect here while everything plays nicely together?
Focus widget
Wrapping fields in a Focus widget might do the trick.
The Focus widget will capture focus loss events for children. With its onFocusChange argument you can call arbitrary functions.
Meanwhile, the onEditingComplete argument of TextField is unaffected and will still be called on the software keyboard "Next/Done" keypress.
This should handle field focus loss for both "Next/Done" keypress and user tapping on another field.
import 'package:flutter/material.dart';
class TextFieldFocusPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// ↓ Add this wrapper
Focus(
child: TextField(
autofocus: true,
decoration: InputDecoration(
labelText: 'Name'
),
textInputAction: TextInputAction.next,
// ↓ Handle focus change on Next / Done soft keyboard keys
onEditingComplete: () {
print('Name editing complete');
FocusScope.of(context).nextFocus();
},
),
canRequestFocus: false,
// ↓ Focus widget handler e.g. user taps elsewhere
onFocusChange: (hasFocus) {
hasFocus ? print('Name GAINED focus') : print('Name LOST focus');
},
),
TextField(
decoration: InputDecoration(
labelText: 'Password'
),
),
],
),
),
),
);
}
}
Please add a focus node to your textfield and add a listener to your focus node to trigger when it unfocuses
final node = FocusScope.of(context);
node.addListener(_handleFocusChange);
void _handleFocusChange() {
if (node.hasFocus != _focused) {
setState(() {
_focused = node.hasFocus;
});
}
}
Widget textInput = TextField(
//you missed this line of code
focusNode: node,
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
And also you can validete automatically by adding autoValidate to your code like below:
Widget textInput = TextField(
//add this line of code to auto validate
autoValidate: true,
textInputAction: textInputAction,
controller: controller,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
keyboardType: textInputType,
));
FocusNode _node;
bool _focused = false;
#override
void initState() {
super.initState();
_node.addListener(_handleFocusChange);
}
void _handleFocusChange() {
if (_node.hasFocus != _focused) {
setState(() {
_focused = _node.hasFocus;
});
}
}
#override
void dispose() {
_node.removeListener(_handleFocusChange);
_node.dispose();
super.dispose();
}
TextFormField(
focusNode: _node)

Why AutoCompleteTextField is not showing any suggestion in Flutter?

I am new to Flutter and currently working on a project where I need to show user a list of matched members so that a user can easily select one of them. For that I use AutoCompleteTextField. It is working fine as long as provided by already fetched list of members to it's suggestion property. But I wonder, why it's not working when I put it under BlocBuilder. Event hits on textChanged method and the state also returns a list but the suggestions are invisible.
Widget autoCompleteSearchBar() {
return BlocBuilder<OrderInfoBloc, MyOrderInfoStates>(
builder: (context, state) {
return AutoCompleteTextField<Member>(
clearOnSubmit: false,
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
decoration: InputDecoration(
hintText: 'Search Member Here..',
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(Icons.cancel),
iconSize: 20,
color: Colors.yellow[700],
onPressed: () {
_autoCompleteController.text = "";
},
),
contentPadding: EdgeInsets.fromLTRB(10, 30, 10, 20),
hintStyle: TextStyle(color: Colors.grey),
),
keyboardType: TextInputType.text,
controller: _autoCompleteController,
textChanged: (value) {
context.read<OrderInfoBloc>().add(SearchTextChanged(text: value));
},
itemSubmitted: (item) async {
_autoCompleteController.text = state.radioGroupValue == 'By Code'
? item.memberNo
: item.memberName;
context.read<OrderInfoBloc>().add(SelectedMember(member: item));
},
key: _key,
suggestions: state.membersList,
itemBuilder: (context, item) {
print(item);
// return state.radioGroupValue == 'By Code'
// ? autoCompleteSearchBarRow(
// item: item.memberNo, icon: Icon(Icons.person))
// : autoCompleteSearchBarRow(
// item: item.memberName, icon: Icon(Icons.person));
return autoCompleteSearchBarRow(
item: item.memberNo, icon: Icon(Icons.person));
},
itemFilter: (item, query) {
print(query);
// bool _itemFilter;
// if (_autoCompleteController.text.isNotEmpty) {
// _itemFilter = state.radioGroupValue == 'By Code'
// ? item.memberNo
// .toLowerCase()
// .startsWith(query.toLowerCase())
// : item.memberName
// .toLowerCase()
// .startsWith(query.toLowerCase());
// } else {
// _autoCompleteController.text = '';
// _itemFilter = false;
// }
// return _itemFilter;
return item.memberNo.toLowerCase().startsWith(query.toLowerCase());
},
itemSorter: (a, b) {
// return state.radioGroupValue == 'By Code'
// ? a.memberNo.compareTo(b.memberNo.toLowerCase())
// : a.memberName.compareTo(b.memberName.toLowerCase());
print(b);
return a.memberNo.compareTo(b.memberNo.toLowerCase());
},
);
}
);
}
Widget autoCompleteSearchBarRow(
{#required String item, #required Icon icon}) {
return ListTile(
leading: icon,
title: Text(item),
);
}
Use the flutter_typeahead package which works well with flutter bloc
Now, come to the bloc side you don't need to wrap your autocomplete widget with blocbuilder cause if you do so, the bloc will always repaint the widget whenever an event fires. so in your case when you are typing in the text box, event fires and bloc rebuild the widget and because of that suggestion don't show up and even if you see suggestion they will be gone once the corresponding bloc state occurs and rebuild the widget
the recommended solution would be seen below
Don't add any state to get suggestions just return the result or records from event as below. (below function added to Cubit file)
Future<List<Item>> getProductItemsBySearchString(String item) async {
return await itemRepository.getItemsByName(item);
}
as you can see above I am returning item records directly from the getProductItemsBySearchString() event method (no bloc state)
Then use It like below
class ItemScreen extends StatelessWidget {
// then you can call bloc event in function as below
Future<List<Item>> getItemSuggestionsList(
BuildContext context, String text) async {
final bloc = context.read<ItemCubit>();
List<Item> data = await bloc.getProductItemsBySearchString(text);
if (data != null) {
return data;
} else {
return null;
}
}
#override
Widget build(BuildContext context) {
return TypeAheadField(
getImmediateSuggestions: true,
textFieldConfiguration: TextFieldConfiguration(
controller: _itemEditingController,
autofocus: false),
suggestionsCallback: (pattern) {
// call the function to get suggestions based on text entered
return getItemSuggestionsList(context, pattern);
},
itemBuilder: (context, suggestion) {
// show suggection list
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(suggestion.name),
trailing: Text(
'Item Code: ${suggestion.code}',
),
),
);
},
onSuggestionSelected: (suggestion) {
// get selected suggesion
},
);
}
}

Flutter TextEditingController retain value without hitting done button

Simple Add Place widget
Title Text Field
Container - Render image from camera
Button - Activates camera device
I thought having a controller connected to TextField would automatically save the state of the input value. However, from my example, if I input the text without click "done" and immediately click on "Take Picture" button. The TextField input value is cleared after coming back from camera operation.
How to Reproduce Problem:
Input text into the field
Immediately click on the Camera button without click done / check or hit enter on the keyboard
Take a picture confirm.
Come back to page the TextField is empty
Example Code:
AddPlacePage StatefulWidget
Column(
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
),
ImageInput(),
],
),
ImageInput StatefulWidget
class _ImageInputState extends State<ImageInput> {
File _storedImage;
Future<void> _takePicture() async {
final imageFile = await ImagePicker.pickImage(
source: ImageSource.camera,
maxWidth: 600,
);
setState(() {
_storedImage = imageFile;
});
...
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
...
child: _storedImage != null
? Image.file(
_storedImage,
fit: BoxFit.cover,
width: double.infinity,
)
: Text(
'No Image Taken',
textAlign: TextAlign.center,
),
alignment: Alignment.center,
),
Expanded(
child: FlatButton.icon(
icon: Icon(Icons.camera),
label: Text('Take Picture'),
textColor: Theme.of(context).primaryColor,
onPressed: () => _takePicture(),
),
),
],
);
}
}
Question:
How can I modify TextField's controller to retain input value even after exiting the application to access device camera?
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
),
I did try to create a local variable and try to use onChange:
String _inputValue
build(BuildContext context){
...
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
onChange: (value) => _inputValue = value;
),
However the effect is the same once returning from the camera as Flutter re-reders the page, both _inputValue and _titleController.text is cleared.
Example code:
https://github.com/erich5168/flutter_camera_example
You can use share preferences to save the String to device then call it when you back to text field. This is how I implement:
class LocalStorage {
static SharedPreferences instance;
static Future init() async {
instance = await SharedPreferences.getInstance();
}
static dynamic getValue(String key, dynamic emptyValue) {
if (LocalStorage.instance.containsKey(key)) {
return LocalStorage.instance.get(key);
}
return emptyValue;
}
}
set it to text field:
TextEditingController _usernameController =
new TextEditingController(text: LocalStorage.getValue('UserName', ''));