TextEditingController vs OnChanged - flutter

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!

Related

Is there any way to generate the same key for the "same" widget in Flutter without having any values to be based on

I have a widget which can have multiple input sections from the same type. When I delete the first child then it behaves weirdly like showing still the old value from that first widget. I figured out I need to use keys for the children but then my UX gets broken. Let me show you some code snippet, please:
class _ParentState extends State<ParentWidget> {
...
#override
Widget build(BuildContext context) {
return Column(
children: stateList
.mapIndexed((index, element) => ChildWidget(key: UniqueKey(), input: element, onChanged: (text) {
setState(() {
stateList[index].text = text;
});
}))
.toList(),
);
}
}
class _ChildState extends State<ChildWidget> {
final ctrl = TextEditingController();
#override
void initState() {
super.initState();
ctrl.text = widget.input.text;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: ctrl,
onChanged: (value) {
widget.onChanged(value);
}
),
...
]
);
}
}
When I wrote the user experience is broken, I was meaning that whenever the user starts to type into the text field then the focus gets loosen character-by-character since the callback is invoked and then generates a new child widget from the state because it has now "different" key... How can I refactor this? I just need a key mechanism which results the same for a child while it is not disposed yet!?
TL;DR
Solution:
remove key
by default, flutter is smart enough to determine which widget is need to rebuild or not. Thats why, key is very rarely used.
ChildWidget(input: element,
onChanged: (text) {
setState(() {
....
When you set UniquieKey() you widget will have different key every time build method called.
which is: everytime you call SetState() your apps will build NEW widget.
if you want to set key, you can use index value
ChildWidget(
key: ValueKey('$index'),
input: element,
onChanged: (text) {
what caused your "user experience broken".
it because, in your onChange function, you call setState() and also you set UniqueKey to your widget. So.. when setState() has called, it will re-execute build method, and since your ChildWidget has Unique key, it will rebuild the widget.
every time user typing, ChildWidget is rebuild. that caused lost focus

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.

Assign value of TextEditingController inside buildMethod

i have some questions about which is the correct way of creating TexEditingController;
Assuming that i want to create a controller with a fixed text, so i can do like this :
TextEditingController bioEditorController = new TextEditingController(text:"dummy");
Now my questions is this:
if i'm usign a stateful widget i can create this controller ad assign an initial text by doing this :
TextEditingController bioEditorController;
#override
void initState() {
bioEditorController = new TextEditingController(text: "dummy");
super.initState();
}
but if i'm not using a stateful widget is it correct to make something like this :
#override
Widget build(BuildContext context) {
TextEditingController bioEditorController =
new TextEditingController(text: controller.profile.value.bio);
What i mean is it correct to create this controller inside the build method, if i do like this it works , but i think that probably is not the best way of doing this things, also becauze i know that controller should also disposed....
I really need some help about clarifying this. Thanks
You are correct in assuming that doing that in the build method is not ideal. You have way less control over how many times the build method runs vs initState.
And while it's generally true that you would need a stateful widget when dealing with TextEditingControllers (or hooks), if you use GetX state management it's absolutely fine, and preferred to not bother with a stateful widget just because you need a TextEditingController.
Another benefit of this way is very easy access to the value of the TextEditingController from anywhere else in the app.
Here's a quick example. This is a class where you can keep all your TextEditingControllers.
class TextController extends GetxController {
RxString textfieldString = ''.obs; // observable String
TextEditingController textController;
// this onInit replaces initState of a stateful widget
#override
onInit() {
super.onInit();
textController = TextEditingController(text: 'dummy');
// adding a listener that automatically updates the textfieldString with the textfield value
textController.addListener(() {
textfieldString.value = textController.text;
debugPrint(textfieldString.value);
});
}
}
Initialize the controller in your main or anytime before you actually use it. This is when the onInit from that class is called.
Get.put(TextController()).textController;
Here's Page1 a stateless widget with an already initialized TextEditingController
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<TextController>(); // finding same initialized controller
return Scaffold(
body: Center(
child: TextFormField(
controller: controller.textController, // using TextEditingConroller from GetX class
),
),
);
}
}
And here's a quick example of a text widget on a different page automatically updating anytime the user types into the TextFormField
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller =
Get.find<TextController>(); // finding same instance of controller
return Scaffold(
body: Center(
// this Obx widget rebuilds based on any updates
child: Obx(
() => Text(controller.textfieldString.value),
),
),
);
}
}
So no matter where you are in your app, you can access the value of that TextFormField and you don't have to use a stateful widget. The GetxController will be removed from memory when not in use.
At this point the only time I ever need to use a stateful widget is when I need the AutomaticKeepAliveClientMixin.
Even animations can be done with stateless widgets in GetX by adding SingleGetTickerProviderMixin to a Getx class and doing everything there that would normally clutter up your stateful widget.

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.

Understanding Flutter didChangeDependencies mechanics

After reading the docs here and the State lifecycle here, I am still not sure about how didChangeDependencies works.
As far as I understand it will be triggered after initState and after any change in an InheritedWidget, but what are these changes? I think it's important to understand what changes trigger didChangeDependencies, so we can understand when and how to use it properly.
When Flutter calls updateShouldNotify() and it returns true, then widgets that requested an inherited widget in build() previously are notified by didChangeDependencies being called.
updateShouldNotify should return true if its state changed since the last time it was called.
TLDR
As the creator of the Widget, you set it's dependencies (InheritedWidget) by using of or maybeOf, etc., so you should understand when they call updateShouldNotify (or they're dependencies call it). It's quite complex:
Usage
You usually don't have to override this method in State because your widget will rebuild when a dependency changes anyway. If you want to do expensive work, like making a network request, then you would avoid making that network request in a normal build, and put this expensive work in didChangeDependencies instead. This allows you to only make these expensive operations when a dependency changes. Tbh, I struggled to think of a situation where you'd want to make an expensive operation purely if an InheritedWidget changes if you're properly structuring your app's logic into services. So I looked at the Flutter framework internals...
Examples of didChangeDependencies overrides:
Flutter's Image widget (the comments are my commentary, and tbh load is probably not the right word I'm using, but resolve isn't much better IMHO)
#override
void didChangeDependencies() {
_updateInvertColors(); // Checks if the image should inverted or not.
_resolveImage(); // Reloads the image if necessary?
if (TickerMode.of(context)) // This method returns a bool: Whether tickers in the given subtree should be enabled or disabled.
_listenToStream(); // Keep loading the image
else
_stopListeningToStream(keepStreamAlive: true); // Be efficient and not read from the stream if not needed. (ticker is false)
super.didChangeDependencies();
}
And when is didChangeDependencies called? aka. what are Image's dependencies (which are all InheritedWidgets)? It uses both of and maybeOf to register the InheritedWidget dependencies, including MediaQuery, Directionality, DefaultAssetBundle, TickerMode, Localizations. So when these dependencies change/ get updated, Image's didChangeDependencies will be called.
What is a dependency?
You make a widget depend on a subtype of InheritedWidget with InheritedWidgetType.of(context), which internally calls context.dependOnInheritedWidgetOfExactType<InheritedWidgetType>();. So the widget has a dependency on that InheritedWidget. For example, Theme.of(BuildContext context) can be seen here.
From the docs inside the Flutter Framework's comments about dependOnInheritedWidgetOfExactType:
Obtains the nearest widget of the given type T, which must be the
type of a concrete [InheritedWidget] subclass, and registers this
build context with that widget such that when that widget changes (or
a new widget of that type is introduced, or the widget goes away),
this build context is rebuilt so that it can obtain new values from
that widget.
Once a widget registers a dependency on a particular type by calling
this method, it will be rebuilt, and [State.didChangeDependencies]
will be called, whenever changes occur relating to that widget until
the next time the widget or one of its ancestors is moved (for
example, because an ancestor is added or removed).
There's more docs, which are really interesting, have a read.
Jitesh's answer is currently just wrong. Dependencies are not Widget's state, they are only relevant for InheritedWidget.
didChangeDependencies() Called when a dependency of this [State] object changes.
So, exactly How it gets called? as by the above definition, it looks like it will be called after state changes but how we come to know the state is changed?
Example:
The below example uses the Provider state management mechanism to update the child widget from the parent widget. The Provider has an attribute called updateShouldNotify which decides whether to state is changed or not. If it's returning true then only didChangeDependencies get called in ChildWidget class.
updateShouldNotify is returning true by default internally, as it knows the state got changed.
Why do we need updateShouldNotify?
It’s needed because if someone wants to update the state on a specific condition. Eg: if UI required to show only even values then we can add a condition like
updateShouldNotify: (oldValue, newValue) => newValue % 2 == 0,
Code Snippet:
class ParentWidget extends StatefulWidget {
ParentWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Life Cycle'),
),
body: Provider.value(
value: _counter,
updateShouldNotify: (oldValue, newValue) => true,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Press Fab button to increase counter:',
),
ChildWidget()
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class ChildWidget extends StatefulWidget {
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
void initState() {
print('initState(), counter = $_counter');
super.initState();
}
#override
void didChangeDependencies() {
_counter = Provider.of<int>(context);
print('didChangeDependencies(), counter = $_counter');
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
print('build(), counter = $_counter');
return Text(
'$_counter',
);
}
}
Output Logs:
I/flutter ( 3779): didChangeDependencies(), counter = 1
I/flutter ( 3779): build(), counter = 1
For more info:
https://medium.com/#jitsm555/differentiate-between-didchangedependencies-and-initstate-f98a8ae43164