Assign value of TextEditingController inside buildMethod - flutter

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.

Related

Which is better when using provider instance in a new widget?

Let's say I've written my code as below.
I've got a provider called SampleProvider, and I'm using it in my main widget.
class SampleProvider extends ChangeNotifier {}
class MainWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
SampleProvider provider = Provider.of<SampleProvider>(context);
}
}
And then, I want to make a new widget and use this provider in the new widget.
There will be two choices.
First, I just instantiate another provider in the new widget as below.
class NewWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
SampleProvider provider = Provider.of<SampleProvider>(context);
}
}
Or, I can send it from the main widget to the new widget as a constructor parameter.
Like this:
class NewWidget extends StatelessWidget {
final SampleProvider provider;
NewWidget(this.provider);
#override
Widget build(BuildContext context) {
}
}
I guess the first option is better because flutter draws a widget based on its build context, but I'm not sure.
I've googled it quite long, but there was no success.
Can anybody tell me whether I am right or wrong? Or Do they have no difference?
Prefer the first solution, it's easier to refactor.
Suppose you need move NewWidget in your widget tree, you also need to modify the "paramter pass" code if you choose second solution, which is not necessary with first solution.
One of Provider pacakage's purpose is avoid passing parameter deep in the widget tree by the way.
Depend on preference not like first or second one.
Have an exception when obtaining Providers inside initState. What can I do?
This exception happens because you're trying to listen to a provider from a life-cycle that will never ever be called again.
It means that you either should use another life-cycle (build), or explicitly specify that you do not care about updates.
As such, instead of:
initState() {
super.initState();
print(context.watch<Foo>().value);
}
you can do:
Value value;
Widget build(BuildContext context) {
final value = context.watch<Foo>.value;
if (value != this.value) {
this.value = value;
print(value);
}
}
which will print value whenever it changes (and only when it changes).
Alternatively, you can do:
initState() {
super.initState();
print(context.read<Foo>().value);
}
SRC: https://github.com/rrousselGit/provider#i-have-an-exception-when-obtaining-providers-inside-initstate-what-can-i-do
Yes, I believe the first option is the better way, of the top of my head I can't think of any situation in which you would prefer the second option to the first.
If you don't use new widget as children of any other widget , first choice is better .
otherwise , second is better .

Clear TextField without controller in Flutter

My screen has multiple textfields, about 15 or so. I don't want to use TextEditingController due to performance reasons as the number of TextFields are likely to grow and I need to pass data from one widget to another back and forth. So I am using OnChanged method of the TextField and am setting a variable which will be used from the parent widget through a function. Now when I click on reset on the parent widget, how do I clear all the values in the TextField controls without using TextEditingController?
class Parent extends StatelessWidget {
String txt='';
myfunction(text)
{
txt=text;
}
#override
Widget build(BuildContext context) {
...
Foo(myfunction);
....
}
}
class Foo extends StatelessWidget {
final Function myfunction;
const Foo(this.myfunction);
#override
Widget build(BuildContext context) {
return TextField(
onChanged: (text) {
myfunction( text);
},...
}
}
You should try to declare all textfields with:
final TextEditingController name = TextEditingController();
final TextEditingController age = TextEditingController();
Create one method like this :
resetAll() {
name.clear();
name.clear();
}
then you call resetAll on reset button like below:
onPressed:() => resetAll()
It's not possible, but the text Fields will be reset if you dispose and reopen the screen holding the text Fields.

Accessing members of a stateful widget in a pageview

I have a screen in my Flutter app that contains a pageview. In that pageview are four stateful widgets. I need to be able to access the members of the first three widgets so I can get the data from them, bring it into the main screen class, and send it to the fourth widget. Below is a model of what I'm trying to accomplish.
I'm thinking that I can do this with methods in each widget's state class but when I create a method there, I can't access it anywhere else even if it's public.
An example of one of the widgets:
import 'package:flutter/material.dart';
class WidgetSample extends StatefulWidget {
#override
_WidgetSampleState createState() => _WidgetSampleState();
}
class _WidgetSampleState extends State<WidgetSample> {
TextEditingController _sampleController = new TextEditingController();
//I want to access this method through an instance of the WidgetSample class
String getTextFromField(){
return _sampleController.text;
}
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: _sampleController,
),
);
}
}
I have tried creating a copy of the method in the widget sample class that calls the method in the state class but that hasn't worked.
Any help would be appreciated. Thanks in advance!
Edit: I have found an answer to my problem provided by BambinoUA on this post:
Controlling State from outside of a StatefulWidget

How to retrieve a TextEditingController inside a Controller layer with Getx?

I have this statement in my View layer
TextEditingController controllerDestino = TextEditingController();
And I want to recover this controllerDestino, to use within a method that is in my Controller layer.
statusUberNaoChamado() {
showBoxAdress = true;
changeMainButton("Chamar", Color(0xFF1ebbd8), () {
callUber("I need pass the controller here");
});
update();}
Thank you in advance for your attention :)
Define/instantiate the TextEditingController as a field inside your GetxController you're using to control your form / implement business logic.
class DestinoFormControllerX extends GetxController {
static DestinoFormControllerX get i => Get.find();
final GlobalKey<FormBuilderState> key = GlobalKey<FormBuilderState>();
// ↓ place the text editing controller inside your... controller :)
var controllerDestino = TextEditingController();
And use the TextEditingController values wherever you need in your GetxController
void resetForm() {
key.currentState.reset();
controllerDestino.text = '';
focusNode.requestFocus();
}
In your View layer, inject your GetxController, and get the text editing controller & access any other methods/fields you need.
class DestinoForm extends StatelessWidget {
final void Function() submitHandler;
DestinoForm({this.submitHandler});
#override
Widget build(BuildContext context) {
final dcx = Get.put(DestinoFormControllerX());
// ↑ inject GetxController, be careful to put *inside* build method
return FormBuilder(
key: dcx.key,
child: Column(
children: [
FormBuilderTextField(
name: 'destino',
controller: dcx.controllerDestino,
decoration: InputDecoration(
labelText: 'Destino',
),
Most forms would have Reset & Submit buttons. There you can call methods on your GetxController....
actions: [
FlatButton(
child: Text('Reset'),
onPressed: () => DestinoFormControllerX.i.resetForm(),
),
Side Note
If you're instantiating / injecting your GetxController in your Form Widget with Get.put(), do so inside the build method of your Form Widget.
Otherwise, you'll likely have TextEditingControllers calling setState on a StatefulWidget (the textfield) that is no longer mounted in the widget tree:
════════ Exception caught by foundation library ════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for TextEditingController:
setState() called after dispose(): _FormBuilderTextFieldState#96390(lifecycle state: defunct, not mounted)
Good
class DestinoForm extends StatelessWidget {
final void Function() submitHandler;
DestinoForm({this.submitHandler});
#override
Widget build(BuildContext context) {
final dcx = Get.put(DestinoFormControllerX());
// ↑ inject GetxController, be careful to put *inside* build method
Bad
class DestinoForm extends StatelessWidget {
final void Function() submitHandler;
final dcx = Get.put(DestinoFormControllerX());
// ↑ wrong place, DestinoFormControllerX gets linked to previous route
DestinoForm({this.submitHandler});
#override
Widget build(BuildContext context) {
More detail on Github, mentioning proper injection / usage of GetX.
I faced with same problem again and found my question after one year.
It's seems that controller should be declared:
final foo = TextEditingController().obs;
And be accessed with:
TextField(
controller: Get.find<SearchFormController>().foo.value,
keyboardType: TextInputType.number,
),
getting value:
print(Get.find<SearchFormController>().foo.value.text);

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!