I'm trying to figure out how to use the TextEditor in Flutter.
I have "Card Editor" (basically I want to be able to work on the equivalent of a paragraph of text)
new EditableText(
autofocus: true,
maxLines: null,
backgroundCursorColor: Colors.amber,
cursorColor: Colors.green,
style: TextStyle(),
focusNode: FocusNode(),
controller: controller,
onSubmitted: (val) {
_addCard(val);
Navigator.pop(context);
},
)
I adapted this from an example of a TextField.
But I have a couple of questions.
Firstly, it doesn't seem to show anything when I type. The cursor moves, but no text is visible. Is that the default when there's no explicit style?
Secondly, how do I trigger the submit? With a TextField, the CR / Enter button does this. Obviously I see why you don't necessarily want that with EditableText But what should I do instead?
Third, I need to be able to put default text into this widget. I tried adding a "value" attribute to the EditableText, but that doesn't seem to be right. What's the way to do this?
from TextField class - material library - Dart API :
EditableText, which is the raw text editing control at the heart of a TextField. The EditableText widget is rarely used directly unless you are implementing an entirely different design language, such as Cupertino.
here an example of TextField , from my app flutter listview CRUD app using nonsecure rest api
class _TaskEditPageWidgetState extends State<TaskEditPageWidget> {
TextEditingController _noteController;
#override
void initState() {
super.initState();
_noteController = TextEditingController.fromValue(
TextEditingValue(
text: widget.taskOpj.note,
),
);
}
#override
void dispose() {
_noteController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(),
body: _body(),
);
}
Widget _appBar() {
return AppBar(
title: new Text("Edit Task"),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.save),
onPressed: _save,
),
],
);
}
Widget _body() {
return SingleChildScrollView(
child: Column(
children: <Widget>[
Text("Note:"),
TextField(
decoration: InputDecoration(border: InputBorder.none),
autofocus: true,
keyboardType: TextInputType.multiline,
maxLines: null,
controller: _noteController),
],
),
);
}
Future _save() async {
widget.taskOpj.note = _noteController.text;
await Tasks.updateTask(widget.taskOpj);
widget.notifyParent();
Navigator.pop(context);
}
}
Just add this line:
style: TextStyle(
decorationThickness: 0.001,
),
Related
I have a page with a column of TextFields. If I populate the TextFields with an existing record I do it through a TextEditingController for each field. I was passing in the QueryDocumentSnapshot from another page but I don't want to do that any longer because I am adding functionality where that no longer works. I can't use StreamProvider because I need some variables that are not available at the top of the tree in order to create the StreamProvider.
I think the best place to create the stream is in the page itself.
So now, I want to get the snapshot and initialize the TextEditingControllers in a method within the class and use them in the "build" method.
I don't know what the scope is of the snapshot that I create using StreamBuilder. How do I access the snapshot in another method in the class?
This is the column code in the build method:
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
reverse: true,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: StreamBuilder(
stream: FirestoreService().getSingleAgencyTrxns(widget.trxnId),
builder: (context, trxnSnapshot) {
if (!trxnSnapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Column(
children: <Widget>[
TextField(
autofocus: true,
keyboardType: TextInputType.text,
controller: clientFNameController,
textAlign: TextAlign.center,
onChanged: (value) {
trxnProvider.changeclientFName(value);
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Client First Name',
labelText: 'Client First Name'),
),
SizedBox(
height: 8.0,
),
TextField(
keyboardType: TextInputType.text,
controller: clientLNameController,
textAlign: TextAlign.center,
onChanged: (value) {
trxnProvider.changeclientLName(value);
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Client Last Name',
labelText: 'Client Last Name'),
),
SizedBox(
height: 8.0,
),
],
);
}
},
),
),
),
),
}
I initialize the TextEditingControllers through a method call in the initState method:
#override
void initState() {
getTrxn();
super.initState();
}
This is the getTrxn() method:
if (globals.currentTrxnId == null || globals.currentTrxnId == "") {
// new record: Set the textFields to blank
clientFNameController.text = "";
clientLNameController.text = "";
} else {
// Get the transaction
clientLNameController.text = someProvider?.clientLName ?? "";
clientTypeController.text = someProvider.clientType ?? "";
}
}
The snapshot variable is visible for every child widget, down from the builder property of the StreamBuilder.
This means that if you really need that info across your app you either:
Create a StreamBuilder on a top-ish level of your app;
Create several StreamBuilder across your app, which listen for the same events and behave accordingly.
The second option might be more complicated but I think that in some real-case scenarios it may be worth it: StreamBuilder re-builds its children when anything changes. Luckily, Flutter is smart enough to not re-build every element on the tree, but it still has to evaluate which Widget is marked as "dirty".
I am pretty new to Flutter (coming from Java) and making my way through the first Android application.
In my project, I am using a different class to create a reusable widget (from the example here), that works perfectly with all methods, but I can't figure out a way to define and reuse the method onEditingComplete.
Here is my code:
class AppTextField extends StatelessWidget {
//
AppTextField({
this.controller,
this.textInputType,
this.pwValidator,
this.editingComplete, // this the method that is causing the problem
});
final TextEditingController controller;
final TextInputType textInputType;
final FormFieldValidator pwValidator;
final Listener editingComplete; // This doesn't work. Am I using the wrong listener?
#override
Widget build(BuildContext context) {
return Container(
child: Theme(
data: ThemeData(
primaryColorDark: Colors.blue,
),
child: Padding(
padding: EdgeInsets.fromLTRB(25, 15, 25, 0),
child: TextFormField(
controller: controller,
keyboardType: null == textInputType ? textInputType : textInputType,
validator: null == pwValidator ? pwValidator : pwValidator,
// I am facing problems with this line of code
onEditingComplete: null == editingComplete ? editingComplete : editingComplete,
),
),
),
);
}
}
This is the class where I want to implement and reuse the widget :
Container(
child: AppTextField(
controller: _controllerPassword,
pwValidator: (value) { },
onEditingComplete: // here is where I am facing difficulties
),
The property onEditingComplete is a VoidCallback. Which is a function without parameter and does not return any data.
In AppTextField define onEditingComplete as
final VoidCallback onEditingComplete;
Then assign it to the onEditingComplete property of the TextFormField. Also, get rid of the ternary operators.
onEditingComplete: onEditingComplete
And when using the widget, pass the callback like so:
Container(
child: AppTextField(
controller: _controllerPassword,
pwValidator: (value) { },
onEditingComplete: (){
//Do what you want to do here.
}
),
I am fairly new at Flutter so I might have gotten things wrong but ... I can't get my head around this issue.
I have ready many posts on how allow multi-line text for TextField / TextFormField.
I have tried setting maxLines to null or to a big number, to enforce max lines (or not), but nothing works !
How come something as simple won't work ?
// in a Scaffold of a StatefulWidget
body: Padding(
padding: const EdgeInsets.all(50.0),
child: TextField(
keyboardType: TextInputType.multiline,
minLines: 1,
maxLines: 5,
),
I have tried on two different real Android devices and hitting the ENTER key does nothing. Unfortunately, I can't test the behavior on iOS as I don't have a Mac.
EDIT 1
Following solution offered by copsonroad, the following code
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.event.shortName == null || widget.event.shortName.isEmpty
? AppLocalizations.of(context).eventEditorTitleNewEvent
: AppLocalizations.of(context)
.eventEditorTitleEditEvent(widget.event.shortName)),
actions: <Widget>[
if (_canSave == true) ...[
IconButton(
icon: Icon(Icons.done),
onPressed: () => _saveEventAndPop(),
)
],
]),
body: Center(
child: Padding(
padding: const EdgeInsets.all(50),
child: SizedBox(
height: 5 * 50.0,
child: TextField(
decoration: InputDecoration(hintText: "Enter a message"),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
),
),
),
);
}
Produce the following result:
Screenshot:
#override
Widget build(BuildContext context) {
var maxLines = 5;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Padding(
padding: const EdgeInsets.all(50),
child: SizedBox(
height: maxLines * 50.0,
child: TextField(
decoration: InputDecoration(hintText: "Enter a message"),
keyboardType: TextInputType.multiline,
maxLines: maxLines,
),
),
),
),
);
}
Edit:
Testing it on Android:
There is actually a bug with TextInputType.multiline. It simply doesn't work with some custom keyboards (i.e. SwiftKey). There is an issue in flutter github where I described my workaround.
First of all, I've wrapped my TextFormField with RawKeyboardListener like this:
RawKeyboardListener(
focusNode: FocusNode(),
autofocus: false,
onKey: (rawKeyEvent) {
final isFormSkippedEnterEvent = rawKeyEvent is RawKeyDownEvent &&
rawKeyEvent.isKeyPressed(LogicalKeyboardKey.enter);
final needToInsertNewLine = isFormSkippedEnterEvent &&
keyboardType == TextInputType.multiline;
if (needToInsertNewLine) {
TextEditingControllerHelper.insertText(controller, '\n');
}
},
child: TextFormField(...)
...
When Gboard is used, RawKeyboardListener doesn't receive Enter event at all, it's sent directly to the text field. But when using for example SwiftKey, instead of inserting new line to the text, RawKeyboardListener receives an event RawKeyDownEvent#a5131(logicalKey: LogicalKeyboardKey#70028(keyId: "0x100070028", keyLabel: "Enter", debugName: "Enter"). I check if I need to insert a new line (no need for this if TextFormField is configured to be single line) and access TextEditingController to alter user's input. All the magic happens inside TextEditingControllerHelper.insertText():
class TextEditingControllerHelper {
static insertText(TextEditingController controller, String textToInsert) {
final selection = controller.selection;
final cursorPosition = selection.base.offset;
if (cursorPosition < 0) {
controller.text += textToInsert;
return;
}
final text = controller.text;
final newText =
text.replaceRange(selection.start, selection.end, textToInsert);
controller.value = controller.value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: selection.baseOffset + textToInsert.length,
),
);
}
}
What I do is check cursor/ selection position. In case of an empty field I just attach a newLine (this helper can work with other inputs, i.e. emojis) and exit. Otherwise I insert a new line on place of existing selection (if there is no selection, text is just inserted where cursor is placed) and move cursor to be after the inserted text. Works fine in my tests (Pixel 3a with Gboard/SwiftKey, Galaxy S8 with Samsung keyboard, iPhone simulator).
I create a profile page and i have 4 textformfield. I want to on tap icon activate textformfield and focus at the same time. Now I need tap twice on icon and first activated field, secondly focused.
How to solve it?
My code:
class UserProfile extends StatefulWidget {
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
FocusNode myFocusNode;
bool isEnable = false;
#override
void initState() {
super.initState();
myFocusNode = FocusNode();
}
#override
void dispose() {
myFocusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.fromLTRB(40.0, 50.0, 20.0, 0.0),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Flexible(
child: TextFormField(
enabled: isEnable,
focusNode: myFocusNode,
),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() {
isEnable = true;
FocusScope.of(context).requestFocus(myFocusNode);
});
})
],
),
You should use autofocus: isEnable instead.
just do like below in your ontap
setState(() {
if(isEnable)
Future.delayed(const Duration(milliseconds: 10), ()
{FocusScope.of(context).requestFocus(myFocusNode);
});
isEnable = true;
});
in first time isEnable is false so focusing not call and just enabling work and in other times get focus too.
you can't focus widget until disabled and when you enabling widget. when you do focusing and enabling at same time in ui tread it's try focusing before enabling because of their rendering time.if you post some delay to focusing the problem get solving.
Try using readOnly instead of enabled in TextFormField
I faced similar issue when I had multiple TextFields to enable kinda PIN input. And some of that had to be dynamically enabled and disabled plus prevent users from entering value in the next field while they haven't finished the previous one. I've tried a lot of approaches and focusing field after some delay was not a way to go because I wanted the keyboard to always be available while entering. So I've took a crazy path and solved this next way:
onTap: () => _focusNodes[_currentLetterIndex].requestFocus()
where _focusNodes are for each letter and _currentLetterIndex is calculated programmatically during input (when finished letter 0 -> current becomes 1 and so on). As the result when user tried to tap on next field - it was automatically refocused to the current one which behaves like the next field is disabled.
An example of the full text field looks like this (don't pay attention to decorations etc.)
TextField(
key: ValueKey(index),
controller: _editingControllers[index],
onTap: () => _focusNodes[_currentLetterIndex].requestFocus(),
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
showCursor: false,
maxLength: 2,
enableInteractiveSelection: false,
autocorrect: false,
enableSuggestions: false,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
height: 1.2,
decoration: TextDecoration.none),
decoration: InputDecoration(
fillColor: !_wordCompleted &&
!correct &&
_editingControllers[index].text.isNotEmpty
? const Color(0xFFFFEEF0)
: Colors.white,
filled: !correct,
counterText: "",
border: defaultInputBorder,
focusedBorder: !correct &&
!_wordCompleted &&
_editingControllers[index].text.isNotEmpty
? incorrectInputBorder
: focusedInputBorder,
enabledBorder: _wordCompleted
? focusedInputBorder
: correct
? correctInputBorder
: defaultInputBorder,
errorBorder: defaultInputBorder,
disabledBorder: _wordCompleted
? focusedInputBorder
: correct
? correctInputBorder
: defaultInputBorder,
contentPadding: const EdgeInsets.only(left: 2)),
),
Solved:
please check my own answer..It's working well..but Is that proper way to fix like this issue?
This is the registration details page. First-row display text-form-field and then dropdown and etc. User typing value and select dropdown and click button information value are showing. But user select dropdown value and then typing information and click the button, information value is null.
AND ALSO typing value on textFormField and click button first time value is null, then click again button value is showing. Why is that?
I added print('textfield is $details'), in onPressed() method.
scenario 1: typing information and click button value is null
scenarion 2: typing information and select dropdown/tap on screen and
click button value is showing
class _overview3 extends State<overview3> {
final detailsController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: overviewAppBar(
detailsController.text),
body: Scaffold(
body: Container(
child: Container(
child: Scaffold(
body: ListView(
children: <Widget>[
TextFormField(
controller: detailsController,
maxLength: 100,
maxLines: 2,
keyboardType: TextInputType.multiline,
validator: (value) {
if (value.length > 3) {
return '100 values';
}
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(20.0),
hintText: AppTranslations.of(context).text("additional_details"),
),
),
],
),
)))
));
}
in my OverviewAppBar method,
overviewAppBar(String details) =>
AppBar(
brightness: Brightness.dark,
backgroundColor: const Color(0xFFE27023),
centerTitle: true,
title: appBarIcon(context),
actions: <Widget>[
IconButton(
icon: _isButtonDisabled
? Icon(
Icons.check,
color: Colors.white30,
)
: Icon(Icons.check),
onPressed: () => _isButtonDisabled
? null
: globals.isButtonClick
? null
:{FocusScope.of(context).requestFocus(FocusNode()),
print('textfield is $details')
}
)
]
);
global.dart file
final globalDetailsController = TextEditingController();
in textformfield
TextFormField(
controller: global.globalDetailsController
)
in OverviewAppBar method in onPressed()
print('textfield is ${global.globalDetailsController}')