I wrote code for custom keyboard in Scaffold and set keyboardType: TextInputType.none to close the default keyboard. The code is working but opposite to expected behavior. When I am focusing on TextField Keyboard disappears and when I am out of focus Keyboard appears. Why is this happening. What is the bug in the code?
class MyKeyBoardApp extends StatelessWidget {
const MyKeyBoardApp({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: KeyboardDemo(),
);
}
}
class KeyboardDemo extends StatefulWidget {
const KeyboardDemo({Key? key}) : super(key: key);
#override
_KeyboardDemoState createState() => _KeyboardDemoState();
}
class _KeyboardDemoState extends State<KeyboardDemo> {
final TextEditingController _controller = TextEditingController();
late FocusNode _focus;
#override
void initState() {
super.initState();
_focus.addListener(_onFocusChange);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
_focus.removeListener(_onFocusChange);
_focus.dispose();
}
void _onFocusChange() {
debugPrint("Focus: ${_focus.hasFocus.toString()}");
}
void buildBottomSheet() {
showModalBottomSheet(
barrierColor: Colors.transparent,
builder: (BuildContext context) {
return CustomKeyboard(
onTextInput: (myText) {
_insertText(myText);
},
onBackspace: () {
_backspace();
},
);
},
context: context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Column(
children: [
const SizedBox(height: 50),
Container(
color: Colors.amber,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _controller,
keyboardType: TextInputType.none,
focusNode: _focus,
onTap: () {
debugPrint(
"Focus in bottom Sheet: ${_focus.hasFocus.toString()}");
buildBottomSheet();
},
),
),
),
],
),
);
}
Related
I have a TextField. I want its text not to be empty. (so I want to know if the text is empty)
I have tried using the following code, but it doesn't work:
controller.text.trim().isEmpty()
My code:
TextFormField(
controller: controller,
),
controller.text.trim().isEmpty()
How to continuously get whether the TextField's text is empty in Flutter? I would appreciate any help. Thank you in advance!
full example:
code:
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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = TextEditingController();
String _text = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Container(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_text),
const SizedBox(height: 20),
TextField(
controller: _controller,
onChanged: (value) {
setState(() {
_text = value;
});
},
decoration: const InputDecoration(
hintText: 'Enter text',
),
),
// submit
ElevatedButton(
onPressed: _text.isEmpty
? null
: () {
setState(() {
_text = _controller.text;
});
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}
It can be done without any temporary variable using ValueListenableBuilder
After some research figured out
controller.text by itself is not listenable
TextEditingController extends ValueNotifier<TextEditingValue> i.e you can use ValueListenableBuilder from material package to listen to text changes.
Code:
class _MyWidgetState extends State<MyWidget> {
late TextEditingController textEditingController;
#override
void initState() {
textEditingController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
TextField(
controller: textEditingController,
),
ValueListenableBuilder<TextEditingValue>(
valueListenable: textEditingController,
builder: (context, value, child) {
return ElevatedButton(
onPressed: value.text.isNotEmpty ? () {} : null,
child: const Text('I am disabled when text is empty'),
);
},
),
],
),
),
);
}
}
Without text:
With text:
You can add listener to your TextEditingController and call setState to update the UI.
late TextEditingController controller = TextEditingController()..addListener(() {
setState((){}); // to update the ui
});
The place you will use controller.text.trim().isEmpty() will show the updated state.
Example
class Test extends StatefulWidget {
const Test({super.key});
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
late TextEditingController controller = TextEditingController()
..addListener(() {
setState(() {}); // to update the ui
});
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: controller,
),
ElevatedButton(
onPressed: controller.text.trim().isEmpty ? null : () {},
child: Text("Button"))
],
);
}
}
My code
class _GenericTextFieldState extends State<GenericTextField> {
#override
Widget build(BuildContext context) {
return CupertinoTextField(
controller: textFieldController,
padding: EdgeInsets.all(8),
prefix: Icon(Icons.email_outlined),
placeholder: widget.hint,
);
}
}
final textFieldController = TextEditingController();
final textFieldProvider = Provider<String> ( (_) => textFieldController.text);
the textFieldController is supplying the string to the textFieldProvider.
I am trying to get the string in another file using the consumer widget like so
class LoadingButton extends ConsumerWidget {
LoadingButton(this.buttonName);
final String buttonName;
#override
Widget build(BuildContext context,ScopedReader watch) {
String textInput = watch(textFieldProvider);
return RoundedLoadingButton(
successColor: mainColor,
errorColor: Colors.orange,
height: 40,
color: mainColor,
child: Text(buttonName, style: TextStyle(color: Colors.white)),
controller: _btnController,
onPressed: (){
mLog("Input from provider username: $textInput");
},
);
}
}
However the textInput variable is always empty.
What am I missing.
you can use onChanged with StateProvider
something like this
Full Example
final textFieldProvider = StateProvider<String>((ref) => "");
class Main extends StatelessWidget {
const Main({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ProviderScope(
child: Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: Column(
children: [
Container(),
_GenericTextFieldState(),
LoadingButton("Test")
],
),
),
);
}
}
class _GenericTextFieldState extends StatelessWidget {
const _GenericTextFieldState({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return CupertinoTextField(
padding: EdgeInsets.all(8),
prefix: Icon(Icons.email_outlined),
placeholder: "Input text",
onChanged: (text) {
context.read(textFieldProvider).state = text;
},
);
}
}
class LoadingButton extends ConsumerWidget {
LoadingButton(this.buttonName);
final String buttonName;
#override
Widget build(BuildContext context, ScopedReader watch) {
String textInput = watch(textFieldProvider).state;
return RaisedButton(
child: Text(buttonName, style: TextStyle(color: Colors.white)),
onPressed: () {
print("Input from provider username: $textInput");
},
);
}
}
I want the focus the focus on the material button so I can press enter or click the button an create an item
final FocusNode _createButtonFocusNode = new FocusNode();
#override
void initState() {
FocusScope.of(context).requestFocus(_createButtonFocusNode);
super.initState();
}
RawKeyboardListener(
focusNode: _createButtonFocusNode,
onKey: (RawKeyEvent event) {
if (event.logicalKey == LogicalKeyboardKey.enter) {
_createItem();
}
},
child:RaisedButton(focusNode: _createButtonFocusNode,
onPressed: () {
_createItem();
},
child: Text("Create"))))
Assume also a cancel material button exists with a _cancelItem event that should be able to accept an enter key on focus
You can copy paste run full code below
You can use _node.requestFocus() to request focus and list keyboard event with FocusAttachment and attach
In demo code, when receive Enter will change button color, see working demo below
code snippet
_node.requestFocus();
...
FocusAttachment _nodeAttachment;
_nodeAttachment = _node.attach(context, onKey: _handleKeyPress);
...
bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
if (event.logicalKey == LogicalKeyboardKey.enter) {
print('clicked enter');
setState(() {
_color = Colors.deepPurple;
});
return true;
}
}
return false;
}
working demo
full code
// Flutter code sample for FocusNode
// This example shows how a FocusNode should be managed if not using the
// [Focus] or [FocusScope] widgets. See the [Focus] widget for a similar
// example using [Focus] and [FocusScope] widgets.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyStatelessWidget(),
),
);
}
}
class CustomButton extends StatefulWidget {
FocusNode focusNode;
CustomButton({Key key, this.focusNode}) : super(key: key);
#override
_CustomButtonState createState() => _CustomButtonState();
}
class _CustomButtonState extends State<CustomButton> {
bool _focused = false;
FocusAttachment _nodeAttachment;
Color _color = Colors.white;
#override
void initState() {
super.initState();
//widget.focusNode = FocusNode(debugLabel: 'Button');
widget.focusNode.addListener(_handleFocusChange);
_nodeAttachment = widget.focusNode.attach(context, onKey: _handleKeyPress);
}
void _handleFocusChange() {
print(widget.focusNode.hasFocus);
if (widget.focusNode.hasFocus != _focused) {
setState(() {
_focused = widget.focusNode.hasFocus;
_color = Colors.white;
});
}
}
bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
if (event.logicalKey == LogicalKeyboardKey.enter) {
print('clicked enter');
setState(() {
_color = Colors.deepPurple;
});
return true;
}
}
return false;
}
#override
void dispose() {
widget.focusNode.removeListener(_handleFocusChange);
// The attachment will automatically be detached in dispose().
widget.focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return Center(
child: RaisedButton(
focusNode: widget.focusNode,
color: _focused ? _color : Colors.white,
child: Text(_focused ? "focused" : 'Not focus'),
onPressed: () {
print("create item");
},
),
);
}
}
class MyStatelessWidget extends StatefulWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
_MyStatelessWidgetState createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
FocusNode _node1 = FocusNode();
FocusNode _node2 = FocusNode();
#override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return DefaultTextStyle(
style: textTheme.headline4,
child: Column(
children: [
CustomButton(
focusNode: _node1,
),
CustomButton(
focusNode: _node2,
),
RaisedButton(
onPressed: () {
_node1.requestFocus();
setState(() {});
},
child: Text("request focus button 1")),
RaisedButton(
onPressed: () {
_node2.requestFocus();
setState(() {});
},
child: Text("request focus button 2")),
],
),
);
}
}
If all you want is for the button to be focused by default, you can do that by just specifying autofocus:true on the button, and you don't even need to create a FocusNode:
class MyCustomWidget extends StatelessWidget {
const MyCustomWidget({Key? key}) : super(key: key);
void _createItem() {
print('Item created');
}
#override
Widget build(BuildContext context) {
return TextButton(
autofocus: true,
child: const Text('CREATE'),
onPressed: _createItem,
);
}
}
This will automatically focus the widget when first built, as long as something else doesn't have the focus already.
If you need to set the focus from another control, you can do that with a focus node, but you don't need to use a FocusAttachment (you rarely, if ever, need to use one of those), you can just pass it to the button and call requestFocus() on it.
class MyCustomWidget extends StatefulWidget {
const MyCustomWidget({Key? key}) : super(key: key);
#override
State<MyCustomWidget> createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget> {
late FocusNode _createButtonFocusNode;
#override
void initState() {
super.initState();
_createButtonFocusNode = FocusNode();
}
#override
void dispose() {
_createButtonFocusNode.dispose();
super.dispose();
}
void _createItem() {
print('Item created');
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
child: const Text('FOCUS OTHER BUTTON'),
onPressed: () => _createButtonFocusNode.requestFocus(),
),
TextButton(
focusNode: _createButtonFocusNode,
child: const Text('CREATE'),
onPressed: _createItem,
),
],
),
);
}
}
(When you do create a FocusNode, be sure to dispose of it properly.)
I have a ListView that has a TextField widget in its children. Listview's items can be changed dynamically. When I press the "Add row" button, a new row should be added and the textfield belongs to newly added row should be focused (keyboard should be shown.) How can I achieve this?
Here is my sample code:
import 'package:flutter/material.dart';
class MainPage extends StatefulWidget {
MainPage({Key key}) : super(key: key);
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
List<String> list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NoteList App'),
),
body: Column(children: <Widget>[
Expanded(child: _buildList(context)),
FlatButton(
onPressed: () {
setState(() {
list.add('new');
});
},
child: Text('Add row'))
]));
}
Widget _buildList(BuildContext context) {
return ListView.builder(
itemCount: list.length,
padding: const EdgeInsets.only(top: 1.0),
itemBuilder: (context, index) {
return ListItem(textContent: list[index]);
},
);
}
}
class ListItem extends StatelessWidget {
var _txt = TextEditingController();
final String textContent;
ListItem({Key key, this.textContent}) : super(key: key) {
_txt.text = textContent ?? '';
}
#override
Widget build(BuildContext context) {
return TextField(
controller: _txt,
textInputAction: TextInputAction.go,
);
}
}
You can copy paste run full code below
You can define an Item class and put FocusNode in it
then use FocusScope.of(context).requestFocus
code snippet
class Item {
String textContent;
FocusNode myFocusNode;
TextEditingController myController;
Item(this.textContent, this.myFocusNode, this.myController);
}
List<Item> list = [
Item('one', FocusNode(), TextEditingController()),
Item('two', FocusNode(), TextEditingController()),
Item('three', FocusNode(), TextEditingController()),
Item('four', FocusNode(), TextEditingController())
];
onPressed: () {
setState(() {
list.add(Item('new', FocusNode(), TextEditingController()));
});
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context)
.requestFocus(list[list.length - 1].myFocusNode);
});
},
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Item {
String textContent;
FocusNode myFocusNode;
TextEditingController myController;
Item(this.textContent, this.myFocusNode, this.myController);
}
class MainPage extends StatefulWidget {
MainPage({Key key}) : super(key: key);
_MainPageState createState() => _MainPageState();
}
List<Item> list = [
Item('one', FocusNode(), TextEditingController()),
Item('two', FocusNode(), TextEditingController()),
Item('three', FocusNode(), TextEditingController()),
Item('four', FocusNode(), TextEditingController())
];
class _MainPageState extends State<MainPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NoteList App'),
),
body: Column(children: <Widget>[
Expanded(child: _buildList(context)),
FlatButton(
onPressed: () {
setState(() {
list.add(Item('new', FocusNode(), TextEditingController()));
});
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context)
.requestFocus(list[list.length - 1].myFocusNode);
});
},
child: Text('Add row'))
]));
}
Widget _buildList(BuildContext context) {
return ListView.builder(
itemCount: list.length,
padding: const EdgeInsets.only(top: 1.0),
itemBuilder: (context, index) {
return ListItem(index: index);
},
);
}
#override
void dispose() {
list.forEach((element) {
element.myFocusNode.dispose();
element.myController.dispose();
});
super.dispose();
}
}
class ListItem extends StatefulWidget {
final int index;
ListItem({Key key, this.index}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
#override
void initState() {
super.initState();
list[widget.index].myController.text = list[widget.index].textContent;
}
#override
Widget build(BuildContext context) {
return TextField(
focusNode: list[widget.index].myFocusNode,
controller: list[widget.index].myController,
textInputAction: TextInputAction.go,
);
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MainPage(),
);
}
}
I have a multi-line Text() inside a ListView item.
By default I only want to show 1 line. When the user taps this item i want it to show all lines. I achieve this by setting the maxLines property of the Text-Widget dynamically to 1 or null.
This works great, but the resizing occurs immediatly but I want to animate this transition.
Here is some example code:
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Example'),
),
body: ListView.separated(
itemBuilder: (context, index) {
return ListItem();
},
itemCount: 3,
separatorBuilder: (_, int index) => Divider(),
),
);
}
}
class ListItem extends StatefulWidget {
ListItem({Key key}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
bool _expanded;
#override
void initState() {
_expanded = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
_expanded = !_expanded;
});
},
child: Text(
'Line1\nLine2\nLine3',
maxLines: _expanded ? null : 1,
softWrap: true,
style: const TextStyle(fontSize: 22),
),
);
}
}
I also already tried using an AnimatedSwitcher like this:
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Example'),
),
body: ListView.separated(
itemBuilder: (context, index) {
return ListItem();
},
itemCount: 3,
separatorBuilder: (_, int index) => Divider(),
),
);
}
}
class ListItem extends StatefulWidget {
ListItem({Key key}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
bool _expanded;
Widget _myAnimatedWidget;
#override
void initState() {
_expanded = false;
_myAnimatedWidget = ExpandableText(key: UniqueKey(), expanded: _expanded);
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
_expanded = !_expanded;
_myAnimatedWidget =
ExpandableText(key: UniqueKey(), expanded: _expanded);
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: 2000),
child: _myAnimatedWidget,
),
);
}
}
class ExpandableText extends StatelessWidget {
const ExpandableText({Key key, this.expanded}) : super(key: key);
final expanded;
#override
Widget build(BuildContext context) {
return Text(
'Line1\nLine2\nLine3',
maxLines: expanded ? null : 1,
softWrap: true,
style: const TextStyle(fontSize: 22),
);
}
}
This animates the Text-Widget but the ListView-Row still resizes immediatly.
What is my mistake? Is the approach of setting the maxLines property maybe wrong for my problem?
Thanks for your help !
Have a great day !
Thanks to Joao's comment I found the right answer:
I just had to wrap my Widget inside the AnimatedSize() widget. That's all :)