I can't use TextEditingController because the TextFormField uses the Autocomplete fieldViewBuilder TextEditingController
Autocomplete(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') return [];
return this
.productNames
.where((Map<String, dynamic> option) {
return option['ar']
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
fieldViewBuilder: (context, textEditingController,
focusNode, onFieldSubmitted) =>
TextFormField(
controller: textEditingController,//uses fieldViewBuilder TextEditingController
focusNode: focusNode,
),
),
AutoComplete has a named parameter initialValue for this.
Autocomplete<AutocompletePrediction>(
initialValue: const TextEditingValue(text: "Initial value"),
fieldViewBuilder: ...
...
)
EDIT
I just realized you are using TextFormField which has a parameter called initialValue. IF you do not need the textediting controller, this answer should work
fieldViewBuilder: (context, textEditingController,
focusNode, onFieldSubmitted) =>
TextFormField(
focusNode: focusNode,
initialValue:"Your initial Value",
),
or if you are using your controller try
fieldViewBuilder: (context, textEditingController,
focusNode, onFieldSubmitted) =>
TextFormField(
controller: textEditingController..text="Initial Value",
focusNode: focusNode,
),
ORIGINAL ANSWER
Why don't you use that text editing controller itself. The code will be something like this
Autocomplete(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') return [];
return this
.productNames
.where((Map<String, dynamic> option) {
return option['ar']
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
fieldViewBuilder: (context, textEditingController,
focusNode, onFieldSubmitted){
textEditingController.text = "Your initial text";// You can use the next snip of code if you dont want the initial text to come when you use setState((){});
return TextFormField(
controller: textEditingController,//uses fieldViewBuilder TextEditingController
focusNode: focusNode,
),
}
),
If you are using setState and the above piece of code keeps replacing the text with the initial text, do something like
Autocomplete(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') return [];
return this
.productNames
.where((Map<String, dynamic> option) {
return option['ar']
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
fieldViewBuilder: (context, textEditingController,
focusNode, onFieldSubmitted){
textEditingController.text = textEditingController.text == ""? "Inital Text":textEditingController.text;// You can use the next snip of code if you dont want the initial text to come when you use setState((){});
return TextFormField(
controller: textEditingController,//uses fieldViewBuilder TextEditingController
focusNode: focusNode,
),
}
),
The last solution is not too elegant but works. I will let you know if I find a better solution. You can even assign this textEditingController to a global variable declared at the top and access it from anywhere in the code. And instead of checking if the text is empty, increase the value of a new variable everytime you setState and if the value is == 0, you can set initial text
This is the solution I found for TextFormField. Making sure the controller is updated at the end of the frame.
fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted) {
SchedulerBinding.instance?.addPostFrameCallback((_) { // <--- this part
var initialValue = someValueThatGotUpdatedDuringBuild;
fieldTextEditingController.text = initialValue;
});
return TextFormField(...);
}
...
Related
I have been on this since days and can't come up with a solution.
I have a PlutoGrid. I want to define a column as an AutoComplete widget with the content of a List.
List<String> autoCompleteData = ["euro", "dollar", "yen"];
PlutoColumn(
title: 'Currency',
field: 'currency',
type: PlutoColumnType.text(),
renderer: (rendererContext) {
return Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
return autoCompleteData;
},
onSelected: (String selection) {
debugPrint('You just selected $selection');
},
fieldViewBuilder:
(context, textEditingController, focusNode, onFieldSubmitted) {
return TextField(
controller: textEditingController,
focusNode: focusNode,
);
},
);
},
),
With this, I can click on the column, but I'm not able to type anything. Since my optionsBuilder is just returning autoCompleteData I can see all the elements of the list.
After some research I came up with this, to control the focus, because PlutoGrid has some kind of issue, when it comes to textfields in the header, footer or in this case a custom textfield:
late final FocusNode focusNodeCustom;
#override
void initState() {
super.initState();
focusNodeMe = FocusNode(onKey: (node, event) {
if (event is RawKeyUpEvent) {
return KeyEventResult.handled;
}
return stateManager.keyManager!.eventResult.skip(KeyEventResult.ignored);
});
Now I have to pass the focusNodeCustom to the TextField in the fieldViewBuilder:
PlutoColumn(
title: 'Currency',
field: 'currency',
type: PlutoColumnType.text(),
renderer: (rendererContext) {
return Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
return autoCompleteData;
},
onSelected: (String selection) {
debugPrint('You just selected $selection');
},
fieldViewBuilder:
(context, textEditingController, focusNode, onFieldSubmitted) {
return TextField(
controller: textEditingController,
focusNode: focusNodeCustom,
);
},
);
},
),
This allows me to type in the TextField, but now the options from autoCompleteData are no longer available.
Either I have the options, but I can't type anything, or I can type, but don't have any options. I know this is a tricky one, but I would be happy about any help.
I create a TextFormField using custom keyboard defined with this library. It works as expected except for one thing: when the text I write is longer than the TextFormField, ended text is hidden. The only way to see it is to click on the TextFormField to force the focus.
I also noticed that by clicking on the up or down arrow corresponding to nextFocus function, I can write to the corresponding TextFormField, but the cursor is not visible. I have to click on the TextField to force the focus and see the cursor. But the scroll problem always remains.
I tried also following this tutorial but it's the same.
I followed this solution but that does not solve the problem.
So do you have a good solution?
final FocusNode _nodeText1 = FocusNode();
final FocusNode _nodeText2 = FocusNode();
var appNotifier = ValueNotifier<String>("");
var keyNotifier = ValueNotifier<String>("");
KeyboardActionsConfig _buildConfig(BuildContext context) {
return KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
keyboardBarColor: Colors.grey[200],
nextFocus: true,
actions: [
KeyboardActionsItem(
focusNode: _nodeText1,
footerBuilder: (_) => HexKeyboard(
notifier: appNotifier,
hexLength: 23,
),
),
KeyboardActionsItem(
focusNode: _nodeText2,
footerBuilder: (_) => HexKeyboard(
notifier: keyNotifier,
hexLength: 47,
),
),
],
);
}
#override
void initState() {
_appController.value =
TextEditingController.fromValue(TextEditingValue(text: "test")).value;
_appkeyController.value =
TextEditingController.fromValue(TextEditingValue(text: "hello")).value;
}
appNotifier = ValueNotifier<String>(_appController.text);
keyNotifier = ValueNotifier<String>(_keyController.text);
super.initState();
}
#override
Widget build(BuildContext context) {
body: KeyboardActions(
tapOutsideBehavior: TapOutsideBehavior.translucentDismiss,
config: _buildConfig(context),
child:...
KeyboardCustomInput<String>(
focusNode: _nodeText1,
notifier: appNotifier,
builder: (context, val, hasFocus) {
_appController.text = val;
_appController.selection =
TextSelection.fromPosition(
TextPosition(
offset:
_appController.text.length),
);
return myTextFormField(
controller: _appController,
maxLength: 23,
keyboardType: TextInputType.none,
focusNode: _nodeText1,
);
}),
KeyboardCustomInput<String>(
focusNode: _nodeText2,
notifier: keyNotifier,
builder: (context, val, hasFocus) {
_keyController.text = val;
_keyController.selection =
TextSelection.fromPosition(
TextPosition(
offset:
_keyController.text.length),
);
return myTextFormField(
controller: _appkeyController,
maxLength: 47,
keyboardType: TextInputType.none,
focusNode: _nodeText2,
showCursor: true);
}),
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)
I want to change the keyboard type dynamic when user changes a Tab in TabBarView. I Tried a lot of things autoFocus = true, give FocusNode() for each TextField and unfocus and request focus for these focusNode. The same FocusNode for these three TextField.
Things that I already tried
return TabBar(
onTap: (_) {
if (tabController.indexIsChanging) {
//here the input just unfocus
FocusScope.of(context).unfocus();
Future.delayed(Duration(seconds: 1)).then((v) {
FocusScope.of(context).requestFocus();
});
}
}, ... )
my TextField
Widget _buildTextField({
TextInputType keyboardType,
List<TextInputFormatter> inputFormatters,
}) {
return BlocBuilder<TransferBloc, TransferState>(
builder: (context, state) {
return TextField(
controller: inputController,
// focusNode: focusInput,
autofocus: true,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
}
)
}
this solves my problem but sounds like a hack.
Does someone have a better approach?
FocusScope.of(context).unfocus();
Future.delayed(Duration(milliseconds: 350)).then((value) {
FocusScope.of(context).requestFocus(inputFocus);
});
less than 350 milliseconds don't work. :/
I have two textFormField widgets. Once the user has completed the first text field I would like to focus on the next textField. Is there a way to do this in Flutter? Currently, the done button just closes the keyboard. I was guessing the focusNode class might be the answer to this but not really sure how that works does anyone have any good examples of focusNode class? Thanks in advance.
Yes, FocusNode and the onFieldSubmitted from a TextFormField are probably the way to go.
FocusScope.of(context).requestFocus(focusNode);
Here is an example that may help:
FocusNode textSecondFocusNode = new FocusNode();
TextFormField textFirst = new TextFormField(
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(textSecondFocusNode);
},
);
TextFormField textSecond = new TextFormField(
focusNode: textSecondFocusNode,
);
// render textFirst and textSecond where you want
You may also want to trigger FocusScope.of() from a button rather than onFieldSubmitted, but hopefully the above example gives you enough context to construct an appropriate solution for your use case.
Screenshot:
No need to use FocusNode
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
TextField(
decoration: InputDecoration(hintText: 'First Name'),
textInputAction: TextInputAction.next,
onEditingComplete: () => FocusScope.of(context).nextFocus(),
),
TextField(
decoration: InputDecoration(hintText: 'Last Name'),
textInputAction: TextInputAction.done,
onSubmitted: (_) => FocusScope.of(context).unfocus(),
),
],
),
);
}
There's a similar method like in Android.
Add
textInputAction
parameter to the TextFormField Widget, then add the property as;
TextInputAction.next
This is how I did it:
var _focusNodes = List.generate(6, (index) => FocusNode()));
And in the TextFormField:
TextFormField(
focusNode: _focusNodes[i],
maxLines: 1,
textInputAction: TextInputAction.next,
onChanged: (text) {
if (i < _controllers.length) {
if (text.isEmpty)
_focusNodes[i - 1].requestFocus();
else
_focusNodes[i + 1].requestFocus();
}
},
),