How to use textEditiing controller with Provider in Flutter - flutter

I am using provider for state management. I am in a situation where there are multiple types of fields in my form. The problem is with text-field
Whenever I change Text it is behaving weirdly like the text entered is displayed in reverse order.
class MyProvider with ChangeNotifier{
String _name;
String get name => _name;
setname(String name) {
_name = name;
notifyListeners();
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final MyProvider myProvider = Provider.of<MyProvider>(context);
final TextEditingController _nameController = TextEditingController(
text: myProvider.name,
);
return TextField(
controller: _nameController,
onChanged: myProvider.setname,
);
}

It happens because new instance of TextEditingController is created on every widget build, and information about current cursor position (TextEditingValue) is getting lost.
Create a controller once in initState method and dispose of it in dispose method.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
TextEditingController _nameController;
#override
void initState() {
final MyProvider myProvider = Provider.of<MyProvider>(context, listen: false);
super.initState();
_nameController = TextEditingController(text: myProvider.name);
}
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final MyProvider myProvider = Provider.of<MyProvider>(context);
return TextField(
controller: _nameController,
onChanged: myProvider.setname,
);
}
}

To store the text from the TextField into Provider you need to send the text property from the controller to the provider:
_nameController.addListener(() {
myProvider.setName(_nameController.text);
});
This would also remove the problem you are getting the reverse text in the TextField

Related

How to access variable of State Stateful widget outside of Widget

I need to realise row of TextField widgets. I did it. But now I want to get actual value TextEditingController from State my TextField. How I can do this?
Its my Stateful widget:
class _UnRecognition extends StatefulWidget {
final String text;
const _UnRecognition({Key? key, required this.text}) : super(key: key);
#override
State<StatefulWidget> createState() => _UnRecognitionState();
}
class _UnRecognitionState extends State<_UnRecognition> {
final TextEditingController editWordController = TextEditingController();
#override
void initState() {
editWordController.text = widget.text;
super.initState();
}
#override
Widget build(BuildContext context) {
return IntrinsicWidth(
child: TextField(
controller: editWordController,
),
);
}
Its, where i want to use my variable:
void _SomeMethodOutsideWidget() {
for (var obj in ListOfWidget) {
if (obj is _UnRecognition) {
**this I get access obj.editWordController.text**
}
}
}
you need to learn the providers: https://pub.dev/packages/provider
basically it is used to retrieve values ​​in the contexts of Statefull Widgets
you can find many tutorial of state managment provider on youtube

How to listen changes inside TextController?

I am using GetX. I need to listen changes in TextController. The follow code do not work:
class Controller extends GetxController{
final txtList = TextEditingController().obs;
#override
void onInit() {
debounce(txtList, (_) {
print("debouce$_");
}, time: Duration(seconds: 1));
super.onInit();
}
}
Is does not print nothing when I am changing txtList value from UI. I suppose it's because it does not check text field inside txtList.
How to get it work?
You need to pass an RxInterface into debounce to do this via GetX. Just create an RxString and add a listener to the controller then pass the RxString into debounce.
class Controller extends GetxController {
final txtList = TextEditingController();
RxString controllerText = ''.obs;
#override
void onInit() {
txtList.addListener(() {
controllerText.value = txtList.text;
});
debounce(controllerText, (_) {
print("debouce$_");
}, time: Duration(seconds: 1));
super.onInit();
}
}
Then on any page in the app you can pass in that controller into the textfield and it'll print the value after the user stops typing for 1 second.
class Home extends StatelessWidget {
final controller = Get.put(Controller());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextField(controller: controller.txtList), // this will print
),
);
}
}
And if you need that value for anything else it's also always accessible via controller.controllerText.value.
By TextEditingController.text, we can already get changing text input value so it does not need .obs.
To pass parameter for debounce, we should pass value itself : txtList.text. (see here: https://github.com/jonataslaw/getx/blob/master/documentation/en_US/state_management.md)
final txtList = TextEditingController(); // 1. here
#override
void onInit() {
debounce(txtList.text, (_) { // 2. here
print("debouce$_");
}, time: Duration(seconds: 1));
super.onInit();
}
This might work.
=================== added 11/21 ==================
Here's the example. I know the RxString variable seems a duplication for TextEditingController.text, but GetX's debounce function needs RxString type variable as a parameter. I tried to find more elegant way to do this, but I couldn't find anything. Please let me know if somebody knows a better way.
// in controller
late final TextEditingController textController;
final RxString userInput = "".obs;
#override
void onInit() {
super.onInit();
textController = TextEditingController();
userInput.value = textController.text;
textController.addListener(() {
userInput.value = textController.text;
}
);
debounce(userInput, (_) {
print("debouce$_");
}, time: Duration(seconds: 1));
}
check this snippet for example to listen to TextEditingController text change listener
import 'package:flutter/material.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
darkTheme: ThemeData.dark(),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final TextEditingController controller = TextEditingController();
#override
void initState() {
super.initState();
controller.addListener(_printLatestValue);
}
void _printLatestValue() {
print('Second text field: ${controller.text}');
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: TextField(
controller: controller,
),
);
}
}

Flutter: How can I retrieve the text from a TextEditingController in a custom StatefulWidget?

I am trying to make a custom stateful widget work in flutter. It is called LetterTextForm, and contains some layout customization and a TextField.
I want it to be easy to retrieve the text that is entered into the TextField of the widget, with some kind of getter method in the widget body that returns TextEditingController.text. The problem is, when I make the TextEditingController controller a final variable like this:
class LetterTextForm extends StatefulWidget {
LetterTextForm({#required this.label, this.prefixIcon});
final Widget prefixIcon;
final String label;
final TextEditingController controller = TextEditingController();
String get textEntered => controller.text;
#override
_LetterTextFormState createState() => _LetterTextFormState();
}
class _LetterTextFormState extends State<LetterTextForm> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
labelText: widget.label,
)
);
}
}
the following error is thrown:
"A TextEditingController was used after being disposed."
I thought maybe the problem was that the controller shouldn't be final, so I moved the controller into the state class and made a function in the state class, textCallback, that modifies a non-final String that is in the widget class. This way works but violates widget immutability:
class LetterTextForm extends StatefulWidget {
LetterTextForm({#required this.label, this.prefixIcon});
final Widget prefixIcon;
final String label;
String text = '';
String get textEntered => text;
#override
_LetterTextFormState createState() => _LetterTextFormState();
}
class _LetterTextFormState extends State<LetterTextForm> {
var controller = TextEditingController();
void textCallback() {
widget.text = controller.text;
}
#override
void initState() {
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
labelText: widget.label,
)
);
}
}
Is there a way to do this that both 1) doesn't violate immutability and 2) doesn't dispose of the TextEditingController() immediately when the user hits Enter?
It works when I get rid of the dispose() method, but that seems like it could cause some performance/memory efficiency issues later. Maybe I don't know what I'm talking about there...
Perhaps I should try something different entirely and place the TextEditingControllers further up in the tree with the ancestors of the LetterTextForm widget, and not internally within the widget?
Create a ValueChanged<String> callback member and call it back when data is completed.
class LetterTextForm{
...
final ValueChanged<String> onChanged;
...
}
class _LetterTextFormState extends ... {
final _controller = TextEditingController();
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(...) {
return TextField(
controller: _controller,
onSubmitted: (value) {
if (widget.onChanged != null) {
widget.onChanged(value); // returns entered value to caller
}
},
);
}
}
P.S. If to use TextFormField then controller is not necessary and you can use initialData for initialization.

Flutter: How to access property from it's state class

I am designing a custom textfield
class MyTextField extends StatefulWidget {
final String labelText;
final TextEditingController textEditingController;
String get text {
// Expected compilation error: "Undefined name '_textEditingController'"
return _textEditingController.text;
}
MyTextField({
Key key,
this.labelText,
this.textEditingController,
.....
.....
}) : super(key: key);
}
class _MyTextFieldState extends State<MyTextField> {
TextEditingController _textEditingController = TextEditingController();
#override
void initState() {
super.initState();
_textEditingController = widget.editingController ?? TextEditingController();
}
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: _textEditingController,
....,
....,
),
)
}
}
I am getting a compilation error Undefined name '_textEditingController' and it is expected as _textEditingController is defined in different class.
Now my question is how I can access _textEditingController property there?
Hope from the above code snippets helps you to understand what I am trying to achieve.
I think this should do what you want. Declare the Controller in the Widget, since you can still access Widget members from State. Just make sure to check if _textEdittingController has a value before using it in your getter.
class MyTextField extends StatefulWidget {
TextEditingController _textEditingController = TextEditingController();
final String labelText;
String get title {
// Error area
return _textEditingController.text;
}
MyTextField({
Key key,
this.labelText,
}) : super(key: key);
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: widget._textEditingController,
),
);
}
}
It should be other way around. Not from state to widget, the data flow should be from widget to state. So declare the below code inside MyTextField widget.
TextEditingController _textEditingController = TextEditingController();
Then you can access the _textEditingController from the state class like below:
widget._textEditingController

TextFormField value is null

I tried to get TextFormField value. But result is null
main page,
children:[
UrlTextField(),
UsernameTextField(),
UrlButton()
]
UrlTextField(), same like UsernameTextField()
class UrlTextField extends StatelessWidget {
final myController = TextEditingController();
#override
Widget build(BuildContext context) {
return AppTextField(
decoration:
InputDecoration(prefixText: "https://", labelText: "Enter your URL"),
myController: myController,
textInputType: TextInputType.url,
);}}
AppTextField() It's a common class, I used this class everywhere
class AppTextField extends StatelessWidget {
final InputDecoration decoration;
var myController = TextEditingController();
final TextInputType textInputType;
AppTextField({
this.decoration,
this.myController,
this.textInputType
});
#override
Widget build(BuildContext context) {
return TextFormField(
controller: myController,
keyboardType: textInputType,
textAlign: TextAlign.left,
decoration: decoration
);}}
I need to get Url and Username value when click button or any other area,
class UrlButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AppButton(
onPressed: () {
String url = UrlTextField().myController.text;
String username = UsernameTextField().myController.text;
print('url is $text');
});}}
AppButton() This class also common
class AppButton extends StatelessWidget {
final VoidCallback onPressed;
AppButton({
this.buttonTextStyle
});
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(...),
onPressed: onPressed);}}
You are trying to retrieve text from a controller which has just been instantiated in the onPressed of the button so there can't be any text so far! To solve this problem you need some way of State Management to access and change an existing widget, in your case the UrlTextField widget. I will give you an example of how you could solve this quickly:
Main page:
class MainPage extends StatefulWidget {
...
#override
createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
UrlTextField _urlTextField = UrlTextField();
...
children:[
_urlTextField,
UsernameTextField(),
UrlButton(_urlTextField)
]
Now we instantiated a UrlTextField which can be referenced to and can be passed to another widget like your UrlButton:
class UrlButton extends StatelessWidget {
final UrlTextField urlTextField;
UrlButton(this.urlTextField);
#override
Widget build(BuildContext context) {
return AppButton(
onPressed: () {
String url = this.urlTextField.myController.text;
String username = UsernameTextField().myController.text;
print('url is $text');
}
);
}
}
On this way you instantiated one UrlTextField and used it in your main page where a user can fill in some input and passed it down to UrlButton where you can access its controller and therefore its text.
I would recommend you to look more into the topic State Management since there are a lot of ways to handle such a case. I can recommend you to take a look on Provider which is very easy to use and convenient to access certain data.
what is the value of text in print('url is $text'); isn't it supposed to be like this print('url is $url');
I think this what you are trying to do.... But there's many loop holes brother..
One thing to remember.. You need separate controllers for each TextField you can't declare one as myController and assign it to all. They'll all have the same value.
class StackHelp extends StatefulWidget {
#override
_StackHelp createState() => _StackHelp();
}
class _StackHelp extends State<StackHelp> {
final TextEditingController myController = TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: new Scaffold(
body: Column(children: <Widget>[
UrlTextField(myController),
// UsernameTextField(),
UrlButton(myController)
])),
);
}
}
class UrlTextField extends StatelessWidget {
final TextEditingController myController;
UrlTextField(this.myController);
#override
Widget build(BuildContext context) {
return AppTextField(
decoration:
InputDecoration(prefixText: "https://", labelText: "Enter your URL"),
myController: myController,
textInputType: TextInputType.url,
);
}
}
class AppTextField extends StatelessWidget {
final InputDecoration decoration;
final TextEditingController myController;
final TextInputType textInputType;
AppTextField({this.decoration, this.myController, this.textInputType});
#override
Widget build(BuildContext context) {
return TextFormField(
controller: myController,
keyboardType: textInputType,
textAlign: TextAlign.left,
decoration: decoration);
}
}
class UrlButton extends StatelessWidget {
final TextEditingController myController;
UrlButton(this.myController);
#override
Widget build(BuildContext context) {
void onPressed() {
String url = this.myController.text;
// String username = UsernameTextField().myController.text;
print('url is $url');
}
return AppButton(onPressed);
}
}
class AppButton extends StatelessWidget {
final VoidCallback onPressed;
AppButton(this.onPressed);
#override
Widget build(BuildContext context) {
return RaisedButton(child: Text('Test'), onPressed: onPressed);
}
}