Estou tendo um bug no dispose dos TextEditingController, quando saiu da pagina
The following assertion was thrown while finalizing the widget tree:
I/flutter (26077): A TextEditingController was used after being disposed.
I/flutter (26077): Once you have called dispose() on a TextEditingController, it can no longer be used.
vi na resposta de outra Inssue que quando dentro de uma lista o nao preciso me preocupar em dar dispose no TextEditingController.
class TextFieldController extends RxController {
TextFieldController(this._validator, {String initialValue = ''}) {
controller = TextEditingController.fromValue(
TextEditingValue(text: initialValue),
);
}
TextEditingController controller;
final error = StringX<String>();
final String Function(String) _validator;
String get text => controller.text;
bool get isValid => error.value == null;
@override
void onInit() {
super.onInit();
print('init textField');
controller.addListener(validar);
}
@override
void onClose() {
super.onClose();
print('close textField');
controller.dispose();
}
void validar() {
error.value = _validator(text);
}
}
TextEditingController usa ValueNotifier com ChangeNotifier, então quando ele for retirado da arvore, ele provavelmente será disposado automaticamente.
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_listeners = null;
}
Edit: eu percebi depois que você adicionou um listener, eu recomendaria sequer usar um TextEditingController nesse caso. Você pode criar uma variável .obs e altera-la pelo onChange. .obs é muito mais poderosa que um TextEditingController, vai permitir que você controle as alterações com um Worker, e certamente será descartada automaticamente.
Vou adicionar isso também à documentação.
Ahhh, saquei
nossa, não sabia essa, tava disposando o controller a toa então kkkk
Nota: (porque eu não tinha visto o addListener).
Se você cria um ouvinte pro seu controller, você deve disposar ele.
Na situação em questão, seria muito melhor usar uma variável alterada por onChange que um TextEditingController, porque além de ser auto disposável, ela pode ser ouvida por um Worker que te dará infinitas possibilidades.
Irei adicionar isso à doc, não tem qualquer necessidade de usar um TextEditingController com o Get, até porque ele usa ValueListenable, que nem se compara com um StringX.
Cara, quando eu criei essa classe eu tinha um motivo pra ta usando um controller, motivo que eu n me lembro mais kkk, mas agora vou usar a abordagem com variavel msm, parece mais pratico e seguro
I will add this to the doc, there is no need to use a TextEditingController with Get, even because it uses ValueListenable, which doesn't even compare with a StringX.
Is the doc for this example already?
As mentioned by @jonataslaw that TextEditingController will be disposed automatically when it is detached from the tree. I decided to verify this by having TextController:
class TextController extends TextEditingController {
@override
void dispose() {
print('$this is being disposed');
super.dispose();
}
}
But I never seen '$this is being disposed' in the console. Here is the full source code:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyAppBinding implements Bindings {
@override
void dependencies() {
// this works fine
// Get.put(SomeService());
// but not this
Get.lazyPut<SomeService>(() => SomeService.shared);
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
initialRoute: '/home',
initialBinding: MyAppBinding(),
getPages: _getPages(),
);
}
List<GetPage> _getPages() {
return [
GetPage(
name: '/home',
page: () => HomePage(),
),
GetPage(
name: '/other',
page: () => OtherPage(),
binding: OtherBindings(),
),
];
}
}
class HomePage extends StatelessWidget {
const HomePage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Home'),
RaisedButton(
onPressed: () {
Get.offAllNamed('/other');
},
child: Text('Go to Other page'),
)
],
),
),
);
}
}
class OtherPage extends StatelessWidget {
const OtherPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetX<OtherBloc>(
builder: (bloc) {
print('counter 1 rebuilt');
return Text(
'Counter: ${bloc.counter1.value}',
);
},
),
GetX<OtherBloc>(
builder: (bloc) {
print('counter 2 rebuilt');
return Text(
'Counter: ${bloc.counter2.value}',
);
},
),
GetX<OtherBloc>(
builder: (bloc) {
print('sum rebuilt');
return Text(
'Counter: ${bloc.sum}',
);
},
),
GetX<OtherBloc>(
builder: (bloc) {
return TextField(
controller: bloc.editingController,
decoration: InputDecoration(
labelText: 'Counter 1 value',
),
);
},
),
GetX<OtherBloc>(
builder: (bloc) {
print('button 1 rebuilt');
return RaisedButton(
onPressed: bloc.increaseCounter1,
child: Text('counter 1 ++'),
);
},
),
GetX<OtherBloc>(
builder: (bloc) {
print('button 2 rebuilt');
return RaisedButton(
onPressed: bloc.increaseCounter2,
child: Text('counter 2 ++'),
);
},
),
RaisedButton(
onPressed: () => Get.offAllNamed('/home'),
child: Text('Go Back'),
),
],
),
),
);
}
}
class OtherBindings implements Bindings {
@override
void dependencies() {
Get.put(OtherBloc());
}
}
class OtherBloc extends BaseBloc {
final editingController = TextController();
var counter1 = 0.obs;
var counter2 = 0.obs;
int get sum => counter1.value + counter2.value;
final someService = Get.find<SomeService>();
@override
void onInit() {
super.onInit();
editingController.addListener(_textChanged);
}
@override
void onClose() {
editingController.removeListener(_textChanged);
super.onClose();
}
void _textChanged() {
counter1.value = int.tryParse(editingController.text) ?? 0;
}
void increaseCounter1() {
counter1.value++;
editingController.text = '${counter1.value}';
}
void increaseCounter2() {
counter2.value++;
}
}
class TextController extends TextEditingController {
@override
void dispose() {
print('$this is being disposed');
super.dispose();
}
}
class BaseBloc extends GetxController {}
class SomeService {
static SomeService shared = SomeService._();
SomeService._();
factory SomeService() {
return shared;
}
}
Edit: eu percebi depois que você adicionou um listener, eu recomendaria sequer usar um TextEditingController nesse caso. Você pode criar uma variável .obs e altera-la pelo onChange. .obs é muito mais poderosa que um TextEditingController, vai permitir que você controle as alterações com um Worker, e certamente será descartada automaticamente.
Vou adicionar isso também à documentação.
I like the idea but correct me if I am wrong, without a TextEditController it is not possible to set a default text, no?
You can use a textFormField, for me it's almost the same and you can put the initial text as a string.
Edit: eu percebi depois que você adicionou um listener, eu recomendaria sequer usar um TextEditingController nesse caso. Você pode criar uma variável .obs e altera-la pelo onChange. .obs é muito mais poderosa que um TextEditingController, vai permitir que você controle as alterações com um Worker, e certamente será descartada automaticamente.
Vou adicionar isso também à documentação.I like the idea but correct me if I am wrong, without a TextEditController it is not possible to set a default text, no?
Not in a TextField, but in a TextFormField, yes.
TextFormField (
initialValue: "value",
);
Actually I don't know why TextField initializes empty, it's not something that the logic prevents.
But sometimes I think about creating a GetEditingController, which I'm sure will be laid out, and which uses the Rx api instead of the TextNotditController ValueNotifier. This would allow an initial value, and could bring all the advantages of Workers, such as debounce, which is very useful in searchs to prevent users from making a kind of DDos on the server while searching for a name.
I create my own textfield controller like this, the validator is a function like the TextFormFIeld receive for validating
```dart
class TextFieldController extends RxController {
TextFieldController(this._validator, {String initialValue = ''}) {
_text.value = initialValue;
}
final _text = StringX
final _error = StringX
final String Function(String) _validator;
String get text => _text.value;
String get error => _error.value;
bool get isValid => _error.value == null;
void validar() {
_error.value = _validator(text);
}
void onChange(String value) {
_text.value = value;
validar();
}
}
```
And use it like here
```dart
final email = TextFieldController(emailValidator)
String emailValidator(String value) {
if (value.isEmpty) return 'Email nao pode ficar vazio';
return null;
}
TextFormField(
initialValue: email.text,
onChanged: email.onChange,
decoration: InputDecoration(
errorText: email.error
),
);
```
with this I can control the error to appear on Text Field as the user starts typing
I will add this to the doc, there is no need to use a TextEditingController with Get, even because it uses ValueListenable, which doesn't even compare with a StringX.
Is the doc for this example already?
Is the document for the example ready, if yes, kindly let us know
As mentioned by @jonataslaw that
TextEditingControllerwill be disposed automatically when it is detached from the tree. I decided to verify this by havingTextController:class TextController extends TextEditingController { @override void dispose () { print ( '$ this is being disposed' ); super . dispose (); } }But I never seen
'$this is being disposed'in the console. Here is the full source code:import 'package: flutter / material.dart' ; import 'package: get / get.dart' ; void main () { runApp ( MyApp ()); } class MyAppBinding implements Bindings { @override void dependencies () { // this works fine // Get.put (SomeService ()); // but not this Get . lazyPut < SomeService > (() => SomeService .shared); } } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build ( BuildContext context) { return GetMaterialApp ( title : 'Flutter Demo' , initialRoute : '/ home' , initialBinding : MyAppBinding (), getPages : _getPages (), ); } List < GetPage > _getPages () { return [ GetPage ( name : '/ home' , page : () => HomePage (), ), GetPage ( name : '/ other' , page : () => OtherPage (), binding : OtherBindings (), ), ]; } } class HomePage extends StatelessWidget { const HomePage ({ Key key, }) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( body : Center ( child : Column ( mainAxisAlignment : MainAxisAlignment .center, children : [ Text ( 'Home' ), RaisedButton ( onPressed : () { Get . offAllNamed ( '/ other' ); }, child : Text ( 'Go to Other page' ), ) ], ), ), ); } } class OtherPage extends StatelessWidget { const OtherPage ({ Key key, }) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( body : Center ( child : Column ( mainAxisAlignment : MainAxisAlignment .center, children : [ GetX < OtherBloc > ( builder : (bloc) { print ( 'counter 1 rebuilt' ); return Text ( 'Counter: $ { bloc.counter1.value }' , ); }, ), GetX < OtherBloc > ( builder : (bloc) { print ( 'counter 2 rebuilt' ); return Text ( 'Counter: $ { bloc.counter2.value }' , ); }, ), GetX < OtherBloc > ( builder : (bloc) { print ( 'sum rebuilt' ); return Text ( 'Counter: $ { bloc.sum }' , ); }, ), GetX < OtherBloc > ( builder : (bloc) { return TextField ( controller : bloc.editingController, decoration : InputDecoration ( labelText : 'Counter 1 value' , ), ); }, ), GetX < OtherBloc > ( builder : (bloc) { print ( 'button 1 rebuilt' ); return RaisedButton ( onPressed : bloc.increaseCounter1, child : Text ( 'counter 1 ++' ), ); }, ), GetX < OtherBloc > ( builder : (bloc) { print ( 'button 2 rebuilt' ); return RaisedButton ( onPressed : bloc.increaseCounter2, child : Text ( 'counter 2 ++' ), ); }, ), RaisedButton ( onPressed : () => Get . offAllNamed ( '/ home' ), child : Text ( 'Go Back' ), ), ], ), ), ); } } class OtherBindings implements Bindings { @override void dependencies () { Get . put ( OtherBloc ()); } } class OtherBloc extends BaseBloc { final editingController = TextController (); var counter1 = 0. obs; var counter2 = 0. obs; int get sum => counter1.value + counter2.value; final someService = Get . find < SomeService > (); @override void onInit () { super . onInit (); editingController. addListener (_textChanged); } @override void onClose () { editingController. removeListener (_textChanged); super . onClose (); } void _textChanged () { counter1.value = int . tryParse (editingController.text) ?? 0 ; } void increaseCounter1 () { counter1.value ++ ; editingController.text = '$ { counter1.value }' ; } void increaseCounter2 () { counter2.value ++ ; } } class TextController extends TextEditingController { @override void dispose () { print ( '$ this is being disposed' ); super . dispose (); } } class BaseBloc extends GetxController {} class SomeService { static SomeService shared = SomeService ._ (); SomeService ._ (); factory SomeService () { return shared; } }
This should be a bug, anyone have a chance to look at this?
Most helpful comment
Is the doc for this example already?