How to focus and highlight a sentence(Text Widget) in Flutter app - flutter

Is there a way to auto focus and highlight a paragraph of text when a page opens
For instance, how Google Docs highlights and focus on paragraph a comment was linked to when comment is clicked.
I want to pass a sentence to a new page, and find where the passed sentence exists on the new page, scroll the sentence into view and highlight the sentence

Create a FocusNode, the Focus Node can be passed to the TextField and it will give focus to the specific TextField on a specific event, e.g a button click or in your case navigating to a new page
class MainWidget extends State<MainWidget> {
FocusNode focusNode;
#override
void initState() {
super.initState();
focusNode = FocusNode();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
focusNode.dispose();
super.dispose();
}
Pass it to the text widget
return TextField(
focusNode: focusNode)
and then your navigation
RaisedButton(
onPressed: //some navigation code
//or () => focusNode.requestFocus()
)

Related

RawKeyboardListener not working on Flutter for Windows

I'm currently working on a Flutter app for Windows, and I try to detect keyboard inputs (like space, arrows, enter...).
For that purpose, I use RawKeyboardListener in my widget:
class _WaveformWidgetState extends State<WaveformWidget>
{
FocusNode _focusNode = FocusNode();
#override
Widget build(BuildContext context)
{
return RawKeyboardListener(
autofocus: true,
focusNode: _focusNode,
onKey: (event) {
if (event.isKeyPressed(LogicalKeyboardKey.enter))
{
print("value : enter");
}
},
child: Container(...),
);
}
}
The problem is: that widget is inside another widget, which contains a form, with multiple TextFormFields, buttons and so on.
And because of that, it seems that the TextFormFields somehow keep the focus and prevent my RawKeyboardListener from working.
So I tried by adding FocusScope.of(context).requestFocus(_focusNode); inside the build() method of that widget, but now my TextFormFields always loose focus when I click on them. Pretty sure it's a focus related problem, but I don't know how to deal with it.
So how can I properly listen to raw keyboard events in my current widget without compromising my form?
Thanks.
OK so I found 2 solutions to my problem:
With RawKeyboardListener:
Add a click listener in the widget, and when clicked, call: FocusScope.of(context).requestFocus(_focusNode);
In the onKey callback of RawKeyboardListener, also add: FocusScope.of(context).requestFocus(_focusNode);
With FocusNode (thanks to that link):
class _WaveformWidgetState extends State<WaveformWidget>
{
FocusNode _focusNode = FocusNode();
late FocusAttachment _focusAttachment;
#override
void initState()
{
super.initState();
_focusAttachment = _focusNode.attach(context, onKeyEvent: (node, event) {
if (event.logicalKey == LogicalKeyboardKey.enter)
{
print("value : enter");
}
return KeyEventResult.handled;
});
_focusNode.requestFocus();
}
#override
void dispose()
{
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context)
{
_focusAttachment.reparent();
return Container(...);
}
}
I personally prefer the second solution, since it seems to work very well, and also allows you to easily handle event like shift+click (check that link again for more info on that).

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.

Will my whole widget tree rebuild when a keyboard appears?

I am trying to build a responsive mobile app so I found an approach were i would divide the sreen into definite number of grids and get the grid width and height and then use this width and height to size my widgets
Question:
I would definitly get my screen's size from MediaQuery.of(context) but since i will only use it once to do my calculations will my widget tree rebuild (assuming i did this calculation in my root widget) whenever a keyboard appears or not? And if it will rebuild should i do the calculations in a different place?
No, if you didn't place any set state or callback during that rebuild the widget when you open the keyboard. However, the issue can be easily resolved by putting your main widget "below" the Scaffold in a SingleChildScrollView to avoid rendering issues.
If you absolutely need to perform actions when the keyboard appears you can use a FocusNode in the textField and add a listener to it with the addListener method. By passing a function to add Listener, you can trigger a setState every time you need, causing the widget to rebuild with the new parameters.
This is a very simplified version of what I mean:
class MyWidget extends StatefulWidget{
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
FocusNode _focusNode;
int state=0;
#override
Widget build(BuildContext context) {
return Container(
height: state==0?100:200, //change the height depending on "state"
child: TextField(
focusNode: _focusNode,
),
);
}
void onFocus(){
setState(() {
//Check if the focus node is focused
if(_focusNode.hasFocus) state=1; //Change the value of the state
});
}
#override
void initState() {
super.initState();
_focusNode=FocusNode();
_focusNode.addListener(onFocus); //Here on focus will be called
}
#override
void dispose() {
super.dispose();
_focusNode.dispose();
}
}

Flutter Widget Focus issue

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

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.