Keep keyboard open when navigating to a new page - flutter

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.

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 to focus and highlight a sentence(Text Widget) in Flutter app

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

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: How to insert text properly in a TextField/TextFormField

Let's say there is an empty TextFormField. After entering 2 characters manually I would like to insert a new one programmatically. So if length equals with 2 than insert a new one. It sounds really simple but strange behaviors appeared while I tried to achieve this. For example: The cursor continuously jumps back to the start and may cause this debug log:
Text selection index was clamped (-1->0) to remain in bounds. This may not be your fault, as some keyboards may select outside of bounds.
Or if I try to deal with the TextEditingController's value or selection property to place cursor at the end of the text, it causes more strange behaviors.
Can you provide me an example by using a TextField or a TextFormField with a TextEditingController and on onChanged() if text length is equals with 2 than insert a new character at the end and place back the cursor also at the end.
I tried these solutions but in this case they did not work:
How do you change the value inside of a textfield flutter?
Thank you!
EDIT: Example code:
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'example',
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final TextEditingController controller = TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
onChanged: (value) {
if (controller.text != null && controller.text.length == 2) {
controller.text = '$value/';
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
setState(() {});
}
},
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
The problem: If I replace the TextFormField to a TextField, it works as expected. I guess it is a bug that should be fixed.
I also found a link that in flutter version 1.20.1 and later this is an issue with TextFormFields.
https://github.com/flutter/flutter/issues/62654
TextFormField is a FormField that contains a TextField. While a Form isn't required to be its parent, using a Form makes it easier to manage multiple fields at once. You can stick with TextField if it works better and meets the requirements.

TextEditingController vs OnChanged

I am looking for a better explanation on the benefit of TextEditingController over OnChanged event for a TextField.
My understanding is that onChanged's setState notifies all widgets of the change in state variable value. This way any widget (e.g. Text) can simply use the state variable and it will be notified of its changes.
My false hopes were TextEditingController would make it even simpler that I won't even need a state variable. Something like below:
import "package:flutter/material.dart";
class TestForm extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestFormState();
}
}
class TestFormState extends State<TestForm> {
//string myStateVariable = "";
final ctrl = TextEditingController();
#override
Widget build(BuildContext context) {
var tf = TextField(
controller: ctrl,
);
var t = Text("Current value: " + ctrl.text); // <<<<<<<<<<< false hope! doesnt work!
var x = Column(children: <Widget>[tf,t],);
return MaterialApp(home: Material(child: Scaffold(
appBar: AppBar(title: Text("Test Form"),),
body: x,
)));
}
}
Can anyone explain why TextEditingController or something similar cannot manage the state itself and notifies all consumers of change in state?
Thanks.
You are just not setting state synchronously that's all. What onChanged does is exactly possible with this approach:
class _TestFormState extends State<TestForm> {
late TextEditingController controller;
#override
void initState() {
controller = TextEditingController()
..addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text('Current Value: ${controller.text}'),
TextField(
controller: controller,
),
],
);
}
}
As you see, we have listener that setting state every time state of the controller changes. This is exactly what onChanged does.
So, about benefits, you can achieve everything with both approach, it's a subjective way.
About benefits:
If you need to hold field values within Stream, onChanged is what you need. In other cases you may use controller.
Actually you won't need both in most of time in my opinion because TextFormField + Form within StatefulWidget is quite complete way to implement form pages. Checkout cookbook: https://flutter.dev/docs/cookbook/forms/validation
TextEditingController actually is managing his own state, that's why you can see the input on the screen once you change it.
You have 2 problems here, the first is that you are not adding any listener to the TextEditingController, you are just asking "give me the current value" only when you build the widget, not "give me the value any time it changes". To achieve this you need to add a listener to the text controller and it will be called every time that the value change.
Try this :
#override
void initState() {
super.initState();
// Start listening to changes.
ctrl.addListener(_printValue);
}
_printValue() {
print("Value: ${ctrl.text}");
}
This will work because print doesn't need to render anything on the screen but if you change it to return a widget it will not work either. That is the second problem, as you pointed out, your parent widget is not been rebuild when the value change, in this case you cannot avoid the setState (or other way to tell flutter that needs to rebuild the widget) when the value change because you need to rebuild the widget to view the change.
Another thing that ill like to point out is that TextEditingController is much powerful and it can be used for more things that just add notifiers to changes. For example if you want a button on other part of the screen that clear the text on a TextField you will need a TextEditingController binded to that field.
Hope it helps!