I have a search bar that opens on click of a button. I added FocusNode and autofocus: true so that when the page is opened, the search bar is automatically focused and the keyboard opens. But the problem is that my keyboard does not open when I open the page. FocusNode not working. What are the problems?
code
class MySearchWidget extends StatefulWidget {
final Function(dynamic)? onChanged;
ValueChanged<bool> isListClear;
MySearchWidget({Key? key, this.onChanged, required this.isListClear})
: super(key: key);
#override
State<MySearchWidget> createState() => _MySearchWidget();
}
class _MySearchWidget extends State<MySearchWidget> {
late FocusNode myFocusNode;
final TextEditingController _textController = TextEditingController();
#override
void initState() {
myFocusNode = FocusNode();
super.initState();
}
#override
void dispose() {
myFocusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
_onBackPressed(context);
},
icon: SvgPicture.asset(
constants.Assets.arrowLeft,
color: constants.Colors.white,
),
),
const SizedBox(
width: 14,
),
Expanded(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: constants.Colors.greyDark,
),
child: TextFormField(
controller: _textController,
focusNode: myFocusNode,
autofocus: true,
onChanged: widget.onChanged,
style: constants.Styles.textFieldTextStyleWhite,
cursorColor: Colors.white,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(top: 13),
border: InputBorder.none,
prefixIcon: Container(
width: 10,
alignment: Alignment.center,
child: SvgPicture.asset(
constants.Assets.search,
),
),
suffixIcon: _textController.text.isNotEmpty
? GestureDetector(
onTap: () {
_textController.clear();
widget.isListClear(true);
},
child: Container(
width: 10,
height: 10,
alignment: Alignment.center,
child: SvgPicture.asset(constants.Assets.remove2),
),
)
: const SizedBox(),
),
),
),
),
],
);
}
Related
The ones I found in different sites all have a search icon on the header and the search bar only appears when it is clicked but I want a search bar that is already there but also with a search button that is connected to it
Illustration:
Try this code(also with the logic to use the Search Functionality). If you like it.
Full Code
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool hasFocus = false;
FocusNode focus = FocusNode();
TextEditingController searchController = TextEditingController();
#override
void initState() {
super.initState();
focus.addListener(() {
onFocusChange();
});
searchController.addListener(() {
filterClints();
});
}
#override
void dispose() {
searchController.dispose();
focus.removeListener(onFocusChange);
super.dispose();
}
void onFocusChange() {
if (focus.hasFocus) {
setState(() {
hasFocus = true;
});
} else {
setState(() {
hasFocus = false;
});
}
}
#override
Widget build(BuildContext context) {
bool isSearching = searchController.text.isNotEmpty;
return Scaffold(
body: Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5,
right: 5,
top: 0,
bottom: 7,
),
child: TextField(
focusNode: focus,
controller: searchController,
// style: TextStyle(fontSize: 14, ),
decoration: InputDecoration(
hintText: "Search",
label: const Text("Search customers & places"),
contentPadding: const EdgeInsets.symmetric(vertical: 2),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(50)),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
),
prefixIcon: const Icon(
Icons.search,
),
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (hasFocus)
InkWell(
onTap: () {},
child: const Icon(
Icons.clear,
color: Colors.grey,
),
),
const SizedBox(
width: 10,
),
PopupMenuButton(
icon: const Icon(Icons.more_vert_sharp,
color: Colors.grey),
itemBuilder: (context) => [
const PopupMenuItem(),
PopupMenuItem(),
],
onSelected: (value) {},
)
],
),
),
),
),
),
],
),
);
}
}
Preview
You can change the values prefixIcon/suffixIcon in textField to suit your needs.
I am implementing AnimatedList on my app with several TextEditingControllers. I would like to dynamically update, insert and remove data. I've read this question and an article on how to update data in an AnimatedList and this is how my code looks:
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
List<Board> boards = [Board.empty()];
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
void Function()? removeItemCallback(int index) {
if (boards.length <= 1) {
return null;
}
return (() {
FocusManager.instance.primaryFocus?.unfocus();
final removedBoard = boards.removeAt(index);
listKey.currentState!.removeItem(
index,
(context, animation) => SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: BoardListItem(
board: removedBoard,
index: index,
removeItemCallback: removeItemCallback(index),
),
),
duration: const Duration(milliseconds: 500));
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return AnimatedList(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
key: listKey,
initialItemCount: boards.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: BoardListItem(
board: boards[index],
index: index,
removeItemCallback: removeItemCallback(index)));
},
);
}
}
class BoardListItem extends StatefulWidget {
const BoardListItem(
{Key? key,
required this.board,
required this.index,
required this.removeItemCallback})
: super(key: key);
final Board board;
final int index;
final void Function()? removeItemCallback;
#override
State<StatefulWidget> createState() => _BoardListItem();
}
class _BoardListItem extends State<BoardListItem> {
late final TextEditingController bigStakeController;
late final TextEditingController smallStakeController;
late final TextEditingController numberController;
#override
void initState() {
super.initState();
print('initing state');
bigStakeController =
TextEditingController(text: widget.board.bigStake.toString());
smallStakeController =
TextEditingController(text: widget.board.smallStake.toString());
numberController =
TextEditingController(text: widget.board.number.toString());
bigStakeController.addListener(() {
if (bigStakeController.text.isNotEmpty) {
widget.board.bigStake = int.parse(bigStakeController.text);
} else {
widget.board.bigStake = 0;
}
});
smallStakeController.addListener(() {
if (smallStakeController.text.isNotEmpty) {
widget.board.smallStake = int.parse(smallStakeController.text);
} else {
widget.board.smallStake = 0;
}
});
}
#override
void dispose() {
print('disposing these');
bigStakeController.dispose();
smallStakeController.dispose();
numberController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.grey, width: 1.5)),
child: Column(children: [
Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color: Colors.grey))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: (() {
numberController.text = '1234';
}),
icon: const Icon(Icons.shuffle)),
Text(
'Board ${widget.index + 1}',
style: const TextStyle(fontWeight: FontWeight.w500),
),
IconButton(
onPressed: widget.removeItemCallback,
icon: const Icon(Icons.delete_outline))
],
),
),
Container(
margin: const EdgeInsets.only(top: 15),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color: Colors.grey))),
child: PinCodeTextField(
controller: numberController,
autoDisposeControllers: false,
autoUnfocus: false,
length: 4,
showCursor: true,
enablePinAutofill: false,
keyboardType: TextInputType.number,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
textStyle: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))
],
animationType: AnimationType.scale,
//errorAnimationController: errorController,
appContext: context,
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(5),
fieldHeight: 50,
fieldWidth: 40,
selectedColor: const Color(0xFFB666D2),
inactiveColor: Colors.grey.shade400,
activeColor: const Color(0xFFB666D2)),
onChanged: (value) {
widget.board.number = value;
},
beforeTextPaste: (text) => false,
)),
IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.3,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 5), //To manage vertical divider height
child: TextFormField(
controller: bigStakeController,
onTap: () {
if (widget.board.bigStake == 0) {
bigStakeController.clear();
}
},
onEditingComplete: () {
if (widget.board.bigStake == 0) {
bigStakeController.text = '0';
}
FocusManager.instance.primaryFocus?.unfocus();
},
enableInteractiveSelection: false,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))
],
keyboardType: TextInputType.number,
decoration: const InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
border: InputBorder.none,
isDense: true,
label: Text('Big'),
),
)),
),
const VerticalDivider(
thickness: 1.5,
color: Colors.grey,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.3,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: TextFormField(
controller: smallStakeController,
onTap: () {
if (widget.board.smallStake == 0) {
smallStakeController.clear();
}
},
onEditingComplete: () {
if (widget.board.smallStake == 0) {
smallStakeController.text = '0';
}
FocusManager.instance.primaryFocus?.unfocus();
},
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))
],
keyboardType: TextInputType.number,
enableInteractiveSelection: false,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
floatingLabelBehavior: FloatingLabelBehavior.always,
label: Text('Small'),
),
),
),
)
],
),
),
])),
);
}
}
When removing the first item, an unexpected behavior happens where the controller's text is sent to the second item. This does not happen when you remove the second item. (e.g Removing board 2).
Upon checking my data source for my second item, nothing was changed too.
Am I implementing TextEditingControllers into an AnimatedList wrongly? And if so, how do I properly implement it?
Is it possible to make the input field immediately active when entering the page, automatically focusing on the TextField and immediately opening the keyboard? I added the autofocus: true method to the TextField but it doesn't help, you still need to click on the input field when the page opens.
widget
Widget build(BuildContext context) {
return Row(
children: [
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
_onBackPressed(context);
},
icon: SvgPicture.asset(
constants.Assets.arrowLeft,
),
),
const SizedBox(
width: 14,
),
Expanded(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(right: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: constants.Colors.greyDark,
),
child: TextField(
autofocus: true,
onChanged: onChanged,
style: constants.Styles.textFieldTextStyleWhite,
cursorColor: Colors.white,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
top: 10,
),
border: InputBorder.none,
prefixIcon: Container(
width: 10,
height: 10,
alignment: Alignment.center,
child: SvgPicture.asset(
constants.Assets.search,
),
),
),
),
),
),
],
);
}
this code can focus, they are almost same to yours.Probably (I am not sure but) your widget trees prevent to focus somewhere.. ---> https://docs.flutter.dev/cookbook/forms/focus check this
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Material App',
home: Material(
child: TestPage(),
),
);
}
}
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
State<TestPage> createState() => TestPageState();
}
class TestPageState extends State<TestPage> {
late final FocusNode focusNode;
#override
void initState() {
focusNode = FocusNode();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: TextField(
autofocus: true,
onChanged: (String ada) {},
cursorColor: Colors.white,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
top: 10,
),
border: InputBorder.none,
prefixIcon: Container(
width: 10,
height: 10,
alignment: Alignment.center,
),
),
),
),
);
}
}
to make your textfield autofocus when the page is open
you need to initial FocusNode and call requestFocus()
here example:
class _SomePageState extends State<SomePage> {
late FocusNode _focusNode;
late TextEditingController _controller;
#override
void initState() {
super.initState();
_focusNode = FocusNode();
_controller = TextEditingController();
_focusNode.requestFocus();
}
#override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: TextField(
controller:_controller,
focusNode:_focusNode
),
),
),
);
}
}
hope this answer your question
I am creating a simple grocery list creator in Flutter. I am trying to go about this by having a plus button that will add ingredient text fields when you press it. Here is what I have done:
body: Container(
padding: EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 30.0),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Text(
'Ingredients ',
style: GoogleFonts.biryani(fontSize: 15.0)),
IconButton(
icon: new Icon(Icons.add),
onPressed: () {
setState(() {
countings++;
});
debugPrint('$countings');
},
)
],
),
SizedBox(height: 10.0),
ListOfIngsWidget(countings, key: UniqueKey())
],
),
)
And here is the ListOfIngsWidget:
class ListOfIngsWidget extends StatefulWidget {
final int countIngs;
const ListOfIngsWidget(this.countIngs, {Key key}) : super(key: key);
#override
_ListOfIngsState createState() => _ListOfIngsState();
}
class _ListOfIngsState extends State<ListOfIngsWidget> {
List<TextEditingController> _controllerList = [];
List<TextEditingController> _numControllerList = [];
List<Widget> _ingList = [];
#override
void initState() {
for (int i = 1; i <= widget.countIngs; i++) {
TextEditingController controller = TextEditingController();
TextField textField = TextField(
controller: controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Ingredient $i',
),
);
TextEditingController numcontroller = TextEditingController();
TextField numField = TextField(
controller: numcontroller,
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: '#', labelText: '#'),
keyboardType: TextInputType.number,
);
_ingList.add(Row(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: SizedBox(
width: 250,
child: textField,
)),
Text('x', style: GoogleFonts.biryani(fontSize: 15)),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: SizedBox(
width: 75,
child: numField,
))
],
));
_controllerList.add(controller);
_numControllerList.add(numcontroller);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return new Container(
child: Flexible(
child: ListView(children: _ingList),
),
);
}
}
The only problem is that if you press the plus button after you have already entered values into one of the textFields, it will clear the field. I kind of understand why this is happening, but is there a way to work around this?
I might be missing some proper disposal of textControllers but here's the gist. As for further reading into keys and why they're necessary, I'd read this medium post
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
final _controllerList = <TextEditingController>[];
final _numControllerList = <TextEditingController>[];
/*
If the user had previous ingredients (say from a db), then you
would fill _controllerList and _numControllerList here using
the old ingredients to populate.
#override
void initState() {
for (ingredient in previousIngredients) {
final controller = TextEditingController(text: ingredient.text);
final numController = TextEditingController();
_controllerList.add(controller);
_numControllerList.add(numController);
}
super.initState();
}
*/
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 30.0),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Text('Ingredients'),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
_controllerList.add(TextEditingController());
_numControllerList.add(TextEditingController());
});
},
),
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
if (_controllerList.isEmpty) return;
setState(() {
_controllerList.removeLast();
_numControllerList.removeLast();
});
},
)
],
),
SizedBox(height: 10.0),
ListOfIngsWidget(_controllerList, _numControllerList),
],
),
),
);
}
}
class ListOfIngsWidget extends StatelessWidget {
ListOfIngsWidget(this.controllerList, this.numControllerList)
: assert(controllerList.length == numControllerList.length);
final List<TextEditingController> controllerList;
final List<TextEditingController> numControllerList;
#override
Widget build(BuildContext context) {
final _ingList = <Widget>[];
for (var i = 0; i < controllerList.length; i++) {
final textField = TextField(
controller: controllerList[i],
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Ingredient $i',
),
);
final numField = TextField(
controller: numControllerList[i],
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: '#', labelText: '#'),
keyboardType: TextInputType.number,
);
_ingList.add(
Row(
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: SizedBox(
width: 250,
child: textField,
)),
Text('x', style: GoogleFonts.biryani(fontSize: 15)),
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: SizedBox(
width: 75,
child: numField,
),
)
],
),
);
}
return Container(
child: Flexible(
child: ListView(children: _ingList),
),
);
}
}
What I need to do is when the onPressed is called, I get the Textfield error when I don't enter text.
class _ExampleDialogTextState extends State<ExampleDialogText> {
FocusNode focusNode = FocusNode();
final textController = TextEditingController();
bool noText = false;
String nameList = "";
#override
void initState() {
super.initState();
nameList = "";
focusNode.addListener(() {
if (!focusNode.hasFocus) {
setState(() {
noText = nameList.length == 0;
});
FocusScope.of(context).requestFocus(focusNode);
}
});
}
TextField(
focusNode: focusNode,
autofocus: true,
controller: textController,
style: TextStyle(
color: Colors.black, fontSize: 14),
decoration: InputDecoration(
counterText: '',
errorText:
noText ? 'Value Can\'t Be Empty' : null,)
RaisedButton(
onPressed: () {
setState(() {
nameList.isEmpty
? noText = true
: noText = false;
});
},)
}
But still, with that code it doesn't work for me. Attached here is the entire class
code
Thank you!
Your Code is correct but you can not update state in ShowDialog Widget, so you have to return Statetful Widget in ShowDialog.
I added whole code which i change.
import 'package:flutter/material.dart';
class Consts {
Consts._();
static const double padding = 16.0;
static const double buttonPadding = 5.0;
}
class DeleteWidget extends StatefulWidget {
const DeleteWidget({Key key}) : super(key: key);
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent,
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialogNameList();
},
backgroundColor: Colors.orange,
child: Icon(
Icons.add,
color: Colors.purple,
size: 40,
),
),
);
}
showDialogNameList() {
return showDialog(
context: context,
builder: (context) {
return CustomeDialog1();
});
}
}
class CustomeDialog1 extends StatefulWidget {
CustomeDialog1({Key key}) : super(key: key);
#override
_CustomeDialog1State createState() => _CustomeDialog1State();
}
class _CustomeDialog1State extends State<CustomeDialog1> {
FocusNode focusNode = FocusNode();
final textController = TextEditingController();
bool noText = false;
String nameList = "";
#override
void initState() {
super.initState();
nameList = "";
focusNode.addListener(() {
if (!focusNode.hasFocus) {
setState(() {
noText = nameList.length == 0;
});
FocusScope.of(context).requestFocus(focusNode);
}
});
}
#override
Widget build(BuildContext context) {
var screenHeight = MediaQuery.of(context).size.height;
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(Consts.padding),
),
elevation: 0.0,
child: Container(
height: screenHeight / 3,
child: Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(
top: Consts.padding,
bottom: Consts.padding,
left: Consts.padding,
right: Consts.padding,
),
margin: EdgeInsets.only(top: 0),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(Consts.padding),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10.0,
offset: const Offset(0.0, 10.0),
),
],
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
TextField(
focusNode: focusNode,
autofocus: true,
controller: textController,
cursorColor: Colors.white,
style: TextStyle(color: Colors.black, fontSize: 14),
decoration: InputDecoration(
counterText: '',
errorText: noText ? 'Value Can\'t Be Empty' : null,
hintText: 'List Name',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.green),
),
labelStyle: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
onChanged: (String text) {
nameList = text;
},
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 150.0,
height: 45.0,
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onPressed: () {
setState(() {
nameList.isEmpty
? noText = true
: noText = false;
});
},
padding:
EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
color: Color(0xFF2DA771),
child: Text('Add',
style: TextStyle(
color: Colors.white,
fontFamily: 'Roboto',
fontSize: 16)),
),
),
],
),
)
],
),
),
)
],
),
));
}
}