Insert into TextField using buttons from another widget - Flutter - flutter

I have a TextField that serves as a search bar where the user can use the built in Android/iOS keyboard to type but also has the possibility to insert a special characters in the search bar from a button. In a way the typing and the other insertion is combined into one string
use case: The user types hell in the search bar then presses the button widget the search bar value becomes : hellö
I set up everything but when I click the button nothing happens (the typing from the keyboard works fine)
Here's my code:
//I have this as a global variable
TextEditingController _searchInputControllor = TextEditingController();
//This is the TextField
class _SearchBarState extends State<SearchBar> {
#override
Widget build(BuildContext context) {
return TextField(
enableInteractiveSelection: false,
controller: _searchInputControllor,
cursorColor: primaryDark,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 15.0),
border: InputBorder.none,
hintText: "Search...",
suffixIcon: Material(
color: Colors.white,
elevation: 6.0,
borderRadius: BorderRadius.all(Radius.circular(6.0),),
child: InkWell(
splashColor: Colors.greenAccent,
onTap: () {},
child: Icon(Icons.search, color: primaryDark,),
),
),
),
);
}
}
//This is the button widget
//It is supposed to add to the search bar but nothing happens
class _SpecialCharState extends State<SpecialChar> {
#override
Widget build(BuildContext context) {
return ButtonTheme(
minWidth: 40.0,
child: FlatButton(
color: Colors.transparent,
textColor: Colors.black,
padding: EdgeInsets.all(8.0),
splashColor: Colors.blue,
onPressed: () {
setState(() {
_searchInputControllor.text = _searchInputControllor.text + widget.btnVal.toLowerCase();
});
},
child: Text(
widget.btnVal
),
)
);
}
}

A. No problem at all
I think your code is working well as I tried on my Android Phone Demo.
The text field is changed as I tap the buttons.
B. Change cursor position
Nonetheless, I add this code to make the cursor automatically placed on last character.
Rather than directly changed the text, we copy its value which contains selection.
Later we offset its selection by length of newText
void appendCharacters() {
String oldText = _searchInputControllor.text;
String newText = oldText + widget.btnVal.toLowerCase();
var newValue = _searchInputControllor.value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: newText.length, //offset to Last Character
),
composing: TextRange.empty,
);
_searchInputControllor.value = newValue;
}
so we can trigger the method with code below :
Widget build(BuildContext context) {
return ButtonTheme(
minWidth: 40.0,
child: FlatButton(
onPressed: appendCharacters, // call a function
),
);
}
Working App Repository
You may look into this repo and build yourself. Github

Related

How do i put checks in my state-management in flutter?

i have a controller that's responsible for hiding and showing a checkbox when a button is pressed.
This is the Controller
class CustomersController extends GetxController {
var isCheboxVisible = false.obs;
void toggleCheckbarVisibility() {
isCheboxVisible.value = !isCheboxVisible.value;
}
}
This is the view
Visibility(
visible: controller.isCheboxVisible.value,
child: Transform.scale(
scale: 1.3,
child: Checkbox(
side: MaterialStateBorderSide.resolveWith(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return const BorderSide(
width: 2, color: Color(0xff34495E));
}
return const BorderSide(
width: 1, color: Color(0xffB0BEC1));
},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
activeColor: Color(0xff34495E),
//materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
//visualDensity: VisualDensity(horizontal: -4, vertical: -4),
value: isChecked,
onChanged: (value) {
setState(() {
this.isChecked = value;
});
},
),
),
)
This is the button that changes the state when clicked
onPressed: () {
customersController.toggleCheckbarVisibility();
},
The problem i have is, sometimes before clicking the button to show the checkbox, the checkbox is already visible and then clicking the button will hide the checkbox which is the opposite of what i want to do.
I want the checkbox to be hidden all the time, and only show when the button is clicked, and then hide when the button is clicked again and this should only be the case.
How do i put this checks in place? Thanks.
I have tried using if's statements in place but i don't seem to be getting it right.

How to implement search page via TextField in Flutter?

I need to make a search page. Made by means of TextField a field on clicking on which the page of search should open. Tell me how to implement clicking on the TextField and so that the back button appears on the left and the buttons disappear on the right?
code
TextFormField(
style: constants.Styles.textFieldTextStyleWhite,
cursorColor: Colors.white,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
top: 15, // HERE THE IMPORTANT PART
),
border: InputBorder.none,
prefixIcon: Align(
alignment: Alignment.centerLeft,
child: SvgPicture.asset(
constants.Assets.search,
width: 20,
height: 20,
))),
)
Normal state
After clicking on the line
Wrap everything into a StatefulWidget.
Then, when clicking the TextFormField, change the attributes of the StatefulWidget.
class YourPage extends StatefulWidget {
_YourPageState createState() => _YourPageState();
}
class _YourPageState extends State<YourPage> {
var myBool = false;
// Initialize your Row-buttons here
// ...
void changeRow(){
setState(() {
// Hide or show Row-buttons here.
myBool = true;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
body: Row(children:[
myBool == true
? Icon( ...) // shows icon
: SizedBox.shrink(), // shows nothing
TextFormField( onTap: () => changeRow() ),
// other BUTTONs here
])
),
);
}
}
There are a few possibilities for an AppBar to show Text or Buttons.
Check these examples:
https://www.fluttercampus.com/tutorial/10/flutter-appbar/

New Textfield created appear with previous textfield's value in flutter

I have this Widget to register. Inside I want to ask for 6 inputs to register, but as not too much space on the screen, I splitted in 2 pair of 3. I show three at first in a form and when the user press the continue button I show the other 3. However, when I press the continue button, the new 3 pair of TextField appear with the same value of the previous ones. And they move position a little under. I don't know why it happens since each of those 6 fields is different Widget function.
I created two variables form1 and form2 to hold the different forms
#override
void initState() {
super.initState();
form1 = <Widget>[
firstForm(),
Text("Or Sign Up with social media"),
SizedBox(
height: 20,
),
socialMediaButtons(),
SizedBox(
height: 50,
),
Text("Have an account? Login")
];
form2 = <Widget>[
secondForm(),
Text("Or Sign Up with social media"),
SizedBox(
height: 20,
),
socialMediaButtons(),
SizedBox(
height: 50,
),
Text("Have an account? Login")
];
}
All the text field have the same format as the text field below, I only changed the variable for their respecting field.
Widget firstNameField() {
return TextFormField(
initialValue: "",
decoration: InputDecoration(
contentPadding: EdgeInsets.only(top: 10, left: 20, right: 20),
border: InputBorder.none,
hintText: "First Name",
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 15)),
onChanged: (val) {
setState(() => firstName = val);
},
);
}
I combined the text fields in two widgets (firstForm and secondForm). (Shown firstForm but it is the same format as second, just called the functions for the other widgets).
Widget firstForm() {
return Column(
children: <Widget>[
emailPassField(),
SizedBox(
height: 50,
),
continueButton(),
SizedBox(
height: 50,
),
],
);
}
Then this is the continue button widget which when pressed. show the second form. I change the step variable to 2 to go to the second form.
Widget continueButton() {
return ButtonTheme(
minWidth: 185.0,
height: 48.0,
child: RaisedButton(
color: Colors.black,
textColor: Colors.white,
child: Text("continue"),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(50.0)),
onPressed: () => setState(() => step = 2),
));
}
When the variable step is changed, I created this function (getForm) to be called and to show the correct form array variable for the children of the column.
#override
Widget build(BuildContext context) {
return Material(
child: Expanded(
child: Stack(
children: <Widget>[
// banner with picture
Positioned(
child: banner(),
),
// Login Elements Container
Positioned(
child: Container(
margin: EdgeInsets.only(top: 300.0),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 5,
blurRadius: 20,
offset: Offset(0, 0))
],
borderRadius: BorderRadius.only(
topRight: Radius.circular(50),
topLeft: Radius.circular(50))),
child: Center(
child: Column(
children: getForm(step),
),
),
),
)
],
),
),
);
}
//functions for switching forms
getForm(int form) {
if (form == 1) {
return form1;
} else if (form == 2) {
return form2;
}
}
This is how the first step of the form appear.
first form
If I don't enter any data in the text fields and press the continue button, the second form with the correct text fields will appear as shown in this below image. You can see that they have the correct hint text.
second form
However if I enter some data on the first step of the form (seen in second form with data step 1), and then press the continue button, in the second step, the text fields will move down a little bit and the same value entered in the previous text fields will appear in the others too(second form with data step 2). can someone help me please, I don't what's going on there? I hope you understand the code and be able to help me please.
second form with data step 1
second form with data step 2
You need to create a TextEditingController for each TextFormField.
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
...
final _cityController = TextEditingController();
...
// initState
...
// dispose of all TextEditingControllers
#override
void dispose {
_emailController.dispose();
...
_cityController.dispose();
super.dispose();
}
// do this for every TextFormField
Widget firstNameField() {
return TextFormField(
// pass the corresponding controller, no need to set initial value if empty
controller: _firstNameController,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(top: 10, left: 20, right: 20),
border: InputBorder.none,
hintText: "First Name",
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 15)),
onChanged: (val) {
setState(() => firstName = val);
},
);
}
Use a unique TextEditingcontroller for each textfield...by this way the values won't overlap each others textfield value.

Flutter: Multiline TextField (TextFormField) simply won't work

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).

Flutter enabled and focus textformfield

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)),
),