1

I made following simple class to allow entering a string:

import 'package:flutter/material.dart';

class MyDialog {
  late TextEditingController _controller;

  Future<String> showMyDialog(BuildContext context) async {
    _controller = TextEditingController();

    final result = await showDialog(
      context: context,
      builder: (BuildContext context) => _buildMyDialog(context),
    );

    _controller.dispose();
    return result;
  }

  Widget _buildMyDialog(BuildContext context) {
    return AlertDialog(
      title: const Text('Enter string: '),
      content: TextField(
        autofocus: true,
        controller: _controller,
      ),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.of(context).pop(_controller.text);
          },
          child: const Text('OK'),
        ),
      ],
    );
  }
}

and I call it like this:

final dialog = MyDialog();
final result = await dialog.showMyDialog(context);

Unfortunately I get following error:

A TextEditingController was used after being disposed.

Why does it happen and how to fix it? I don't use the controller anywhere after disposing it after all.

MartinYakuza
  • 60
  • 1
  • 12
  • 1
    Looks like it's because you're calling `_controller.dispose();` at the end of the `showMyDialog` method. Therefore, it invalidates that controller for the TextField it is attached to. Why are you calling `_controller.dispose();` in that method? Also, why are you creating the controller instance in the `showMyDialog` method? – daddygames Jan 14 '22 at 20:20
  • @daddygames well, I was told that this controller should be always disposed or the memory will leak (or something like that). – MartinYakuza Jan 14 '22 at 20:43
  • take a look at my answer. You can decide which direction you want to go after you read my reasoning for a different approach and research how each option may affect your project. – daddygames Jan 14 '22 at 22:20

2 Answers2

3

My concern with Yeasin's answer (at time of writing) is that you are not calling dispose() on the controller and I'm not 100% certain what nulling out the instance does in that regard. It may avoid the error, but maybe there is a memory leak or other problem (hypothetically).

Another approach I can suggest would be to turn your AlertDialog into a StatefulWidget so that you can take advantage of the built-in dispose() method for Widgets. This would allow you to avoid micro-managing the controller. Instead, you call the dispose method for the controller inside the dispose method of the Widget in which it is contained.

Here is what your code could look like to take advantage of this feature of Flutter and it does not change how you call the code to bring up the dialog:

class MyDialog {
    
    Future<String> showMyDialog(BuildContext context) async {
      final result = await showDialog(
        context: context,
        builder: (BuildContext context) => MyAlertDialog(),
      );
      return result;
    }
    
}

class MyAlertDialog extends StatefulWidget {
    @override
    State<MyAlertDialog> createState() => MyAlertDialogState();
} 

class MyAlertDialogState extends State<MyAlertDialog> {
    
        // for this example, it's safe to instantiate the controller inline
        TextEditingController _controller = new TextEditingController();
        
        @override
        void dispose() {
            // attempt to dispose controller when Widget is disposed
            try { _controller.dispose(); } catch (e) {}
            super.dispose();
        }
        
        Widget build(BuildContext context) {
            return AlertDialog(
                title: const Text('Enter string: '),
                content: TextField(
                    autofocus: true,
                    controller: _controller,
                ),
                actions: [
                    TextButton(
                        onPressed: () {
                            Navigator.of(context).pop(_controller.text);
                        },
                        child: const Text('OK'),
                    ),
                ],
            );
        }
    
}
Raymond Holmboe
  • 2,061
  • 1
  • 16
  • 18
daddygames
  • 1,880
  • 1
  • 11
  • 18
1

On showMyDialog it is getting new TextEditingController every time, we can comment _controller.dispose();. Also, it is possible to get null on this method, therefore it will be better using

Future<String?> showMyDialog(...){...}

Another thing we can do making it nullable

class MyDialog {
  TextEditingController? _controller;

  Future<String?> showMyDialog(BuildContext context) async {
    _controller = TextEditingController();

    final String? result = await showDialog(
      context: context,
      builder: (BuildContext context) => _buildMyDialog(context),
    );
    _controller = null;
    return result;
  }
//....

You can check about _dependents.isEmpty': is not true

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56