i have a textfield that require user to call another user using # sign. So if the user press # there should be a popup at the top of the textfield.
i have try using this package but if does not suit what i want...
here is an example.
it should only show when user type # only....
With the TextField you use it has the onChanged method to catch all the changes made to the text field input and with that you can evaluate the first character of the text field and that's what I'd recommend you to.
With my example, it prints every change made in the input of the textfield. It also checks only the first character of the input and prints to the console. You also can use setState instead of print to for example sets a boolean and then handles the Plugin you use.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late TextEditingController _controller;
#override
void initState() {
super.initState();
_controller = TextEditingController();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextField(
controller: _controller,
onChanged: (String value) {
value.characters.first == '#'
? print(
'Contains #',
)
: print(
'Does not contain #',
);
},
),
),
);
}
}
In flutter_typeahead package, you can add your logic in the suggestionsCallback.
In the example below, i am only calling the api and showing the data if the text starts with #. Modify as per your requirements.
TypeAheadField(
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
style: DefaultTextStyle.of(context).style.copyWith(
fontStyle: FontStyle.italic
),
decoration: InputDecoration(
border: OutlineInputBorder()
)
),
suggestionsCallback: (pattern) async {
if("${pattern}".startsWith("#")){
return await BackendService.getSuggestions(pattern);
}else{
return [];
}
},
itemBuilder: (context, suggestion) {
return ListTile(
leading: Icon(Icons.shopping_cart),
title: Text(suggestion['name']),
subtitle: Text('\$${suggestion['price']}'),
);
},
onSuggestionSelected: (suggestion) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProductPage(product: suggestion)
));
},
)
Related
I have problems following step by step what happens when onChanged is triggered on my TextField. Especially, I have a problem understanding where and why the variable value gets its actual value in the following example.
Example:
class felder extends StatefulWidget {
felder({super.key});
String textFieldName = "";
#override
State<felder> createState() => _felderState();
}
class _felderState extends State<felder> {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
obscureText: false,
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: 'Name'),
onChanged: (value) => widget.textFieldName = value,
)
],
);
}
}
How I always imagined it: I think flutter passes a function in the background, which has a parameter value, that has the content of the TextField.
Actually TextField is a widget that has its own state.
Whenever user types something, the value in a TextField
changes.
At that time, a callback is fired from the TextField.
The changed value is also passed along with the
callback.
Using onChanged: (value){ print(value); } , we can
get the value from that callback and use it as per our needs.
From TextField source code,
The text field calls the [onChanged] callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the [onSubmitted] callback.
To get the value from a TextField, you can also use TexteditingController.
First declare TextEditingController controller = TextEditingController();.
Then inside your TextField, add the controller like this
TextField(
controller: controller,
),
Then to get the value from controller, you can use controller.value.text.
What is a callback?
From GeeksForGeeks:
Callback is basically a function or a method that we pass as an
argument into another function or a method to perform an action. In
the simplest words, we can say that Callback or VoidCallback are used
while sending data from one method to another and vice-versa
Creating a callback
To create your own callback, you can use ValueChanged.
Code example:
Let's create our own button, that when the onChanged is called, it will give us a new value:
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
Usage:
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
Complete example
You can run/paste this example in your editor, and take a look:
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
See also
How to pass callback in Flutter
What's in onChanged Docs ?
ValueChanged<String>? onChanged
onChanged is of type ValueChanged<String> and is called when the user initiates a change to the TextField's value: when they have inserted or deleted text.
This callback doesn't run when the TextField's text is changed programmatically, via the TextField's controller. Typically it isn't necessary to be notified of such changes, since they're initiated by the app itself.
What is Callback ?
If we go by definition, the Callback is a function or a method which we pass as an argument into another function or method and can perform an action when we require it.
For Example, if you are working in any app and if you want any change in any value then what would you do?
Here you are in a dilemma that what you want to change either state() or a simple value/values. If you need to change states then you have various state-changing techniques but if you want to change simple values then you will use Callback.
Refer this article to understand the callback on event of textChange this will surely make you understand the core behind the mechanism
I want to autofill several textfields with one suggestion, like for example: If I select Washington as a state where I live I want the other field that would be country field to fill itself with U.S.
Thanks for your attention!
You will need to use setState( ) inside the onChanged. inside that setState, you will change the value of the other field otherDropdownValue . Here is a small example with dropDownMenus.
Dont forget you need a StatefulWidget (not StateLess)
Code:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String dropdownValue = 'One';
String otherDropdownValue = 'Two';
#override Widget build(BuildContext context) {
return Column(children: [
DropdownButton<String>(
value: dropdownValue,
onChanged: (String? newValue) {
//******************************************
//*****Here is what you are looking for*****
//******************************************
setState(() {
dropdownValue = newValue;
otherDropdownValue = newValue; ///Changes the other one
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value),);}).toList(),
),
DropdownButton<String>(
value: otherDropdownValue,
onChanged: (String? newValue) {
setState(() {
otherDropdownValue = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value),);}).toList(),
),
],
);
}
}
Let me know if this does not help?
EDIT to answer your last comment:
Same logic to apply with a TextField or a textformfield.
You will need to add a TextEditingController() to control the text displayed.
Below is a fully working example (the part you need to look at is at the end)
and here is a link that explains the code (note I adjusted the code for your specific use case)
https://flutter.dev/docs/cookbook/forms/text-field-changes
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
// Create a text controller and use it to retrieve the current value
// of the TextField.
final myController = TextEditingController();
#override
void initState() {
super.initState();
// Start listening to changes.
myController.addListener(_printLatestValue);
}
#override
void dispose() {
// Clean up the controller when the widget is removed from the widget tree.
// This also removes the _printLatestValue listener.
myController.dispose();
super.dispose();
}
void _printLatestValue() {
print('Second text field: ${myController.text}');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
///********************
///**** LOOK HERE ****
///********************
TextField(
onChanged: (text) {
myController.text = text;
},
),
TextField(
controller: myController,
),
],
),
),
);
}
}
I have 4 textFormField widgets. Once the user has completed the first text field I would like to focus on the next textField automatically. Is there a way to do this in Flutter? anyone please share , thank in advance :)
This can be done in Flutter in different ways, and I'll try to share the simplest one of them. Before getting into the answer, it's worth mentioning the following issue:
Detect when delete is typed into a TextField #14809
In Flutter, backspace does not send any event when the TextField is empty (i.e. TextField.onChanged won't be called). In your case, if the user is at third field and they press backspace to return to the second field, there's no way to capture that key press without some workaround that were discussed in the linked issue. In short, you'll need to add a zero-width space character (it doesn't get rendered but is present in the String) to detect backspace events.
I mentioned this issue because I'm sharing an example that utilize the zero-width space character (zwsp for short).
In the following example, I simply created two lists that contains:
FocusNode for each field
TextEditingController for each field.
Based on the index, you can bring the focus to a specific field by calling:
FocusNode.requestFocus().
Similarly, you can remove the focus by calling FocusNode.unfocus or you can remove any focus from anywhere by calling: FocusScope.of(context).unfocus(); (in the example below, it's used after the last character is inserted to hide the keyboard).
That being said, here's a full example that you can copy and paste to try it out:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(child: CodeField()),
);
}
}
/// zero-width space character
///
/// this character can be added to a string to detect backspace.
/// The value, from its name, has a zero-width so it's not rendered
/// in the screen but it'll be present in the String.
///
/// The main reason this value is used because in Flutter mobile,
/// backspace is not detected when there's nothing to delete.
const zwsp = '\u200b';
// the selection is at offset 1 so any character is inserted after it.
const zwspEditingValue = TextEditingValue(text: zwsp, selection: TextSelection(baseOffset: 1, extentOffset: 1));
class CodeField extends StatefulWidget {
const CodeField({Key key}) : super(key: key);
#override
_CodeFieldState createState() => _CodeFieldState();
}
class _CodeFieldState extends State<CodeField> {
List<String> code = ['', '', '', ''];
List<TextEditingController> controllers;
List<FocusNode> focusNodes;
#override
void initState() {
// TODO: implement initState
super.initState();
focusNodes = List.generate(4, (index) => FocusNode());
controllers = List.generate(4, (index) {
final ctrl = TextEditingController();
ctrl.value = zwspEditingValue;
return ctrl;
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// give the focus to the first node.
focusNodes[0].requestFocus();
});
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
focusNodes.forEach((focusNode) {
focusNode.dispose();
});
controllers.forEach((controller) {
controller.dispose();
});
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
4,
(index) {
return Container(
width: 20,
height: 20,
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: controllers[index],
focusNode: focusNodes[index],
maxLength: 2,
keyboardType: TextInputType.number,
decoration: InputDecoration(
counterText: "",
),
onChanged: (value) {
if (value.length > 1) {
// this is a new character event
if (index + 1 == focusNodes.length) {
// do something after the last character was inserted
FocusScope.of(context).unfocus();
} else {
// move to the next field
focusNodes[index + 1].requestFocus();
}
} else {
// this is backspace event
// reset the controller
controllers[index].value = zwspEditingValue;
if (index == 0) {
// do something if backspace was pressed at the first field
} else {
// go back to previous field
controllers[index - 1].value = zwspEditingValue;
focusNodes[index - 1].requestFocus();
}
}
// make sure to remove the zwsp character
code[index] = value.replaceAll(zwsp, '');
print('current code = $code');
},
),
);
},
),
);
}
}
You may want to use a FocusNode on each of your TextFormField, this way, once your user has enter text in the TextFormField, you can use in the callback onChanged of the TextFormField call myNextTextFieldFocusNode.requestFocus()
FocusNode textFieldOne = FocusNode();
FocusNode textFieldTwo = FocusNode();
// ...
TextFormField(
onChanged: (_) {
textFieldTwo.requestFocus();
},
focusNode: textFieldOne,
controller: textController,
)
You can use onChanged and nodefocus properties. When onchanged called refer to next textfield.
init a focus node ;
late FocusNode myFocusNode;
#override
void initState() {
super.initState();
myFocusNode = FocusNode();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
myFocusNode.dispose();
super.dispose();
}
onChanged property;
TextField(
focusNode: myFocusNode1,
onChanged: (text) {
myFocusNode2.requestFocus();// I could not remember the correct usage please check
},
),
I want to pass a variable name as a function parameter, but it doesn't seem to work : the content of my variable remains unchanged.
Widget Field(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
onChanged: (value) {
setState(() {
changedValue = value;
});
},
obscureText: isTextObscured,
);
}
Here, I want to change the value of the variable who has the name "changedValue". When I do it directly with the variable name, it works, but when I try to use the parameter, nothing happens. Here's an example of where I used it :
Widget LoginFields() {
return Column(
children: [
Field(email, Strings.emailLabel, false),
Field(password, Strings.passwordLabel, true),
ValidationButton(),
],
);
}
Thanks in advance!
There are many things to clarify here, like:
setState() is a method, that must be called inside a StatefullWidget.
if you create a function, name it with lowerCamelCase (effective dart).
for returning a Widget prefer extend a Widget, especially if you need a State.
if you seek a guide for TextField in Flutter - check cookbook here and here.
Here how you can set it up:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: Text('Material App Bar'),
),
body: Center(
child: Column(
children: [
FieldWidget(changedValueInitial: 'email', label: 'labelConstOne'),
FieldWidget(changedValueInitial: 'password', label: 'labelConstTwo', isTextObscured: true),
// ValidationButton(),
],
),
),
),
);
}
}
class FieldWidget extends StatefulWidget {
String changedValueInitial;
String label;
bool isTextObscured;
FieldWidget({
Key? key,
required this.changedValueInitial,
required this.label,
this.isTextObscured = false,
}) : super(key: key);
#override
_FieldWidgetState createState() => _FieldWidgetState();
}
class _FieldWidgetState extends State<FieldWidget> {
late String _changedValue;
#override
void initState() {
super.initState();
_changedValue = widget.changedValueInitial;
}
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(labelText: widget.label),
// validator: yourValidator,
initialValue: _changedValue,
onChanged: (value) {
setState(() {
_changedValue = value;
});
},
obscureText: widget.isTextObscured,
);
}
}
If that is what you need..
So, I'm new to Flutter and was trying to code a simple notes app to learn my way around it. The layout of the app is a HomePage with a ListView of NoteTiles that when tapped open a corresponding NotePage where you can write down things. The issue I got is that every time I leave the NotePage and then re-open it from the HomePage, the NotePage loses its content.
My first idea was to keep the content in the corresponding NoteTile so that when I leave the NotePage I would pop with the content, and when needed I would push to the NotePage with the previously saved content. The problem is that I didn't find any simple way to push and set the content. I've seen there are Notification and Route methods but they come with quite a lot of boilerplate and they look like it's more for passing data from child to parent, which I can do easily when popping.
So, is there a way to avoid the reset of the content of the NotePage? Or maybe is there a simple way to initState with the content previously saved?
Here is the code I have so far:
class NoteTile extends ListTile {
final NotePage note;
final Text title;
final BuildContext context;
NoteTile(this.title, this.note, this.context) : super(
title: title,
onTap: () => {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => note),
),
},
onLongPress: () => null,
);
void switchToNote() async {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => note),
);
}
}
onLongPress will later be used to delete the note.
class NotePage extends StatefulWidget {
final String title;
NotePage({Key key, this.title}) : super(key: key);
#override
_NotePageState createState() => _NotePageState();
}
class _NotePageState extends State<NotePage> {
TextEditingController _controller;
String _value;
void initState() {
super.initState();
_controller = TextEditingController();
_controller.addListener(_updateValue);
_value = '';
}
void dispose() {
_controller.dispose();
super.dispose();
}
void _updateValue(){
_value = _controller.text;
}
Future<bool> _onWillPop() async {
Navigator.pop(context, _value);
return true;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintStyle: TextStyle(fontStyle: FontStyle.italic),
hintText: 'New note',
),
maxLines: null,
controller: _controller,
),
padding: EdgeInsets.all(12.0),
),
),
onWillPop: _onWillPop,
);
}
}
The _onWillPop is to send back the content to the NoteTile, which currently disregards the return data because I failed to find a way to use that data later when pushing the NotePage again.
ListTile is StatelessWidget widget, so you cannot reserve the state, the NoteTile should be StatefulWidget.
Here is a sample of NoteTile:
class NoteTile extends StatefulWidget {
final Text title;
const NoteTile({Key key, this.title}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _NoteTileState();
}
}
class _NoteTileState extends State<NoteTile> {
String _result;
#override
void initState() {
super.initState();
_result = "";
}
void _onOpenNote() async {
String result = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => NotePage(title: _result)),
);
if (result != null) {
_result = result;
}
}
#override
Widget build(BuildContext context) {
return ListTile(
title: widget.title,
onTap: _onOpenNote,
);
}
}
And NotePage should be edit in some lines:
TextEditingController can have initialize string & have to initialize by title:
_controller = TextEditingController(text: widget.title);
Navigator.pop can post result and you did it correctly, but if you want to get the result, should wait for Navigator.of(context).push, because its run in another thread.