Getx: TextEditingController dentro de um RxController vai ser disposado automaticamente?

Created on 12 Jun 2020  ·  13Comments  ·  Source: jonataslaw/getx

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);
  }
}

Most helpful comment

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?

All 13 comments

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 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;
  }
}

This should be a bug, anyone have a chance to look at this?

Was this page helpful?
0 / 5 - 0 ratings