Flutter Widget Focus issue - flutter

I've create a custom widget for time picking.
The widget contains an icon which opens a TimePicker, and a TextFormField so the user can type the time value manually.
This is how it looks like:
When the user types a value it immediately validated and also when the focus is off, it validate the value and update the field with correct time format.
For example, if the user types 8 and then clicks next widget, it will update to 8:00
Checkout the form image with 2 TimePickers:
What I want is that when user types StartTime, and then clicks the keyboard Next button, the focus will move to the EndTime picker. Then when the user clicks Next button on the EndTime Picker the focus will move to the next widget in the form
The problem is that the FocusNode is inside the TimePicker custom widget (which is StateFull) and I couldn't figure out how it can be exposed outside of it.
You can see the code for it here:
https://github.com/moti0375/tikal_time_tracker/blob/develop/lib/ui/time_picker.dart
Any idea will be appreciated.

Finally I've figured it out,
Instead of creating the FocusNode inside the picker widget (a child widget), I've created the FocusNode in the parent widget (the form) and provide it to the child widget in its constructor, by this the focus node created in the parent widget context.
Then, I've added a request focus method to the child widgets so the parent can call them and FocusScope.of(context).requestFocus(focusNode); is called inside the child widgets but on the focusNode that provided by the parent widget.
Here is a portion of the code:
Child widget:
class TimeTrackerTimePicker extends StatefulWidget {
final FocusNode focusNode;
TimeTrackerTimePicker({ this.focusNode});
//This can be called from the parent widget with the parent context
void requestFocus(BuildContext context){
print("${this.pickerName} requestFocus...");
FocusScope.of(context).requestFocus(focusNode);
}
....
....
#override
State<StatefulWidget> createState() {
return TimePickerState();
}
}
State class:
class TimePickerState extends State<TimeTrackerTimePicker> {
#override
Widget build(BuildContext context) {
return Container(
....
child: new Flexible(
child: new TextFormField(
textInputAction: TextInputAction.next,
focusNode: widget.focusNode, //linking to the focusNode
onFieldSubmitted: onSubmitButtonClicked,
decoration: InputDecoration(
hintText: widget.hint != null ? widget.hint : "",
contentPadding:
EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0))),
maxLines: 1,
controller: pickerController))
)
}
}
Then in the parent widget when you need to set focus:
FocusNode focusNode;
TimeTrackerTimePicker timePicker;
#override
void initState() {
super.initState();
focusNode = new FocusNode();
timePicker = new TimeTrackerTimePicker(focusNode: focusNode);
}
.....
//request focus when required..
void requestPickerFocus(){
timePicker.requestFocus(context);
}

FocusScope.of(context).unfocus()

Related

Flutter Newbie: Modifying Textfield value breaks focus on TextField

Go easy. I just started learning Flutter a week ago. I'm coming from ReactJS so I have a decent understanding of state management and lifecycle methods. But I'm completely new to Dart and Flutter and how it handles state.
I am writing a quick WebRTC chat application. I have a TextField I'm using to generate room names. I decided I wanted to make the labelText of the TextField, cycle through some random words, every 5 seconds, while the field is not in focus. If the field comes into focus, I stop cycling the label. I do this so that the field appears to have a pre generated random room name.
I am having trouble editing the TextField. I assume this is an issue with setState or my TextEditingController. I'm used to being able to access an input's value, so controllers are odd to me.
Here is my ChangingTextField:
import 'dart:async';
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
//
class ChangingTextField extends StatefulWidget {
final TextEditingController controller;
ChangingTextField({
Key? key,
required this.controller,
}) : super(key: key);
#override
_ChangingTextFieldState createState() => _ChangingTextFieldState();
}
class _ChangingTextFieldState extends State<ChangingTextField> {
FocusNode _focusNode = FocusNode();
Timer? _timer;
String _roomName = "example.com/";
bool _wasFocused = false;
#override
void initState() {
super.initState();
_focusNode = FocusNode();
_timer = Timer.periodic(Duration(seconds: 5), (Timer t) => _genRoomName());
}
#override
void dispose() {
_timer?.cancel();
_focusNode.dispose();
super.dispose();
}
void _requestFocus(){
if(!_wasFocused){
setState(() {
_timer?.cancel();
_wasFocused = true;
FocusScope.of(context).requestFocus(_focusNode);
});
}
}
void _genRoomName(){
WordPair wp = generateWordPairs().take(1).first;
setState(() => _roomName = "example.com/" + wp.first + "-" + wp.second );
}
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
focusNode: _focusNode,
controller: widget.controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: _wasFocused ? "example.com/" : _roomName,
),
onTap: _requestFocus,
),
);
}
}
The parent widget simply passes a TextEditingController into this widget so that I can listen for changes, and (I assume) gather the value of the TextField at a later point in time.
The listener is defined like this in the parent widget:
#override
void initState() {
roomNameController.addListener(() {
setState(() {});
});
super.initState();
}
However, every time I try to change the value of the TextField, after every character that I type, the focus is broken on the ChangingTextField widget, and I must click again inside the TextField to type my next character. I am assuming this issue is because the listener calls setState in the parent widget.
In React terminology I would refer to this as a re-render. If the parent re-renders, the child goes with it, and so the app loses what knowledge it had of where in the widget tree the user was working. However, I feel that the controller needs to exist in the parent, such that, I can acquire the value of the child when needed (e.g. on a button press). Lifting state up and whatnot.
Can someone explain to me what is going on here?
I found the solution. Listening inside of the widget instead of initializing the listener in the parent component, produces the behavior you would expect.
In short, moving the following code:
#override
void initState() {
roomNameController.addListener(() {
setState(() {});
});
super.initState();
}
into the ChangingTextField widget's initState as opposed to having it in the parent's initState, resolved the problem. Best of all, the controller is still created by the parent, so the controller's text is available in the parent when the submit button is pressed.

How is the class variable being updated without calling setstate?

How am I able to get the updated value of the TextFormField without using setState((){})?
On TextFormField 's onChanged method, I am setting the class variable to the value, but normally we would need setState((){}) for the value to be updated. I am just wondering how is this working?
class ResetPasswordPage extends StatefulWidget {
const ResetPasswordPage({ Key? key }) : super(key: key);
#override
_ResetPasswordPageState createState() => _ResetPasswordPageState();
}
class _ResetPasswordPageState extends State<ResetPasswordPage> {
String _currentPassword = '';
void onChanged(){
print('current Password is: ');
print(_currentPassword); ----------->this is printing the updated value, without setstate?
}
#override
Widget build(BuildContext context) {
return Container(
TextFormField(
onChanged: (val) => {
_currentPassword = val,
onChanged()
},
),
)
}
}
The variable value does change without the need to use setState, setState is only needed to tell Flutter that the UI has to change to reflect changes in your data.
From Flutter docs:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
You can create Notifier -> listener, then set the listener to listen for the Notifier,so any update on the Notifier(controller)it will notify the listener(TextFormField) to update itself.
Like this:
final _controller = TextEditingController();
child: TextFormField(
controller: _controller,
decoration: //decoration,
),
when you call this _controller.text it will notify all the listeners of this TextEditingController that needs update, then you get the same result setState((){}) will give you.

Flutter: How to hide TextField text pointer (cursor) when use initial value text (Android)

Use case: Messaging app you edit your message: keyboard, blinking cursor and initial text appears but pointer (cursor) is not
But on Flutter when you use initial text (or via textController) there are always pointer(cursor) which is not wanted
Example
Steps to reproduce:
run flutter create bug
edit main.dart to replace center text (line 100) to MyStatefulPage(),
class MyStatefulPage extends StatefulWidget {
#override
State<MyStatefulPage> createState() {
return _MyStatefulPageState();
}
}
class _MyStatefulPageState extends State<MyStatefulPage> {
TextEditingController controller;
#override
void initState() {
super.initState();
controller = new TextEditingController();
controller.text = 'My Initial Text';
}
#override
Widget build(BuildContext context) {
return TextField(
decoration: InputDecoration(
border: InputBorder.none
),
// showCursor: false,
controller: controller,
autofocus: true,
maxLines: 8,
);
}
}
With that code when you open app keyboard will appear but so will pointer(cursor) I want to hide this cursor(pointer).
Note: it's only for Android.
TextField set enableInteractiveSelection property to false can resolve this issue
TextFormField cursorHeight: 0 and cursorWidth: 0 can hide the cursor.
in textformfield use showCursor: false

Keep keyboard open when navigating to a new page

I have an app with two pages - each page contains a textfield and the first page also contains a "Next" button that will navigate to page 2.
My questions is this: When the textfield on page 1 has focus and I push the "Next button" the keyboard will close before navigating to the next screen.
Is there a way to suppress this behaviour so the keyboard will stay open instead of first closing and the opening again when page 2 is shown?
You can try
SystemChannels.textInput.invokeMethod('TextInput.hide'); for hiding
and SystemChannels.textInput.invokeMethod('TextInput.show'); for showing keyboard
And it needs to add import 'package:flutter/services.dart';
You could use a technique similar to https://stackoverflow.com/a/58906112 . Specifically, create a Stack at the top of your application with a hidden text field. Focus that text field whenever you need to keep the keyboard visible and navigate between pages. Example code below.
You will also need a way for widgets on pages to access the hidden widget's FocusNode object. I recommend using ScopedModel.
class KeepKeyboardOnScreen extends StatefulWidget {
final FocusNode focusNode;
const KeepKeyboardOnScreen({#required this.focusNode});
#override
State createState() => KeepKeyboardOnScreenState();
}
class KeepKeyboardOnScreenState extends State<KeepKeyboardOnScreen> {
TextEditingController _controller;
#override
void initState() {
super.initState();
_controller = new TextEditingController();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => Container(
height: 0,
child: ClipRect(
child: CupertinoTextField(
controller: _controller,
focusNode: widget.focusNode,
onChanged: (_) => _controller.clear(),
),
),
);
}
If autofocus is set to true for the TextField in your second page and the keyboard is active prior to hitting the next button in your first page, the keyboard will remain same during the page transition.

How to listen to keyboard on screen Flutter?

I am building a mobile app, I want to remove a widget when the keyboard appears on the screen, i.e when the input text field is on focus.
I have tried to use RawKeyboardListener but that doesn't seem to work, my code is as below:
new Container(
child: new RawKeyboardListener(
focusNode: new FocusNode(),
onKey: (input) => debugPrint("*****KEY PRESSED"),
child: new TextField(
controller: new TextEditingController(),
),
),
);
You can use this simple check:
MediaQuery.of(context).viewInsets.bottom == 0
The keyboard is closed when this returns true, otherwise it's open.
Be aware to take the context of the whole screen (Scaffold for example) and not only from one widget.
This is how you integrate that check to your code:
Visibility(
child: Icon(Icons.add),
visible: MediaQuery.of(context).viewInsets.bottom == 0,
)
The keyboard will automatically appear when the text field is focused. So you can add a listner to the focusnode to listen the focus change and hide respective widget.
Example:
void _listener(){
if(_myNode.hasFocus){
// keyboard appeared
}else{
// keyboard dismissed
}
}
FocusNode _myNode = new FocusNode()..addListener(_listner);
TextField _myTextField = new TextField(
focusNode: _mynNode,
...
...
);
new Container(
child: _myTextField
);
I used the package keyboard_visibility
Then I wrapped my TextField with a KeyboardListener implemented as follows:
class KeyboardListener extends StatefulWidget {
final Widget child;
final void Function(bool) onChange;
KeyboardListener({#required this.child, #required this.onChange});
#override
_KeyboardListenerState createState() => _KeyboardListenerState();
}
class _KeyboardListenerState extends State<KeyboardListener> {
int _sId;
KeyboardVisibilityNotification _kvn;
#override
void initState() {
super.initState();
_kvn = KeyboardVisibilityNotification();
_sId = _kvn.addNewListener(
onChange: widget.onChange,
);
}
#override
Widget build(BuildContext context) {
return widget.child;
}
#override
void dispose() {
_kvn.removeListener(_sId);
super.dispose();
}
}
You can use this library keyboard_visibility: ^0.5.6 at :
https://pub.dev/packages/keyboard_visibility
For execute your code, insert this in the initState()
KeyboardVisibilityNotification.addNewListener(
onChange: (bool visible) {
print(visible);
this.setState(() {
keyboardIsOpen = visible;
});
},
);
Whenever keyboard is open or closed, the library calls onChange method with the visibility boolean.
A widget that calls a callback whenever the user presses or releases a key on a keyboard.
A RawKeyboardListener is useful for listening to raw key events and hardware buttons that are represented as keys. Typically used by games and other apps that use keyboards for purposes other than text entry.
For text entry, consider using a EditableText, which integrates with on-screen keyboards and input method editors (IMEs).
const RawKeyboardListener({
Key key,
#required FocusNode focusNode,
#required ValueChanged<RawKeyEvent> onKey,
#required Widget child
})
Creates a widget that receives raw keyboard events.
For text entry, consider using a EditableText, which integrates with on-screen keyboards and input method editors (IMEs).
Implementation
const RawKeyboardListener({
Key key,
#required this.focusNode,
#required this.onKey,
#required this.child,
}) : assert(focusNode != null),
assert(child != null),
super(key: key);