skip to Main Content

I’m trying to implement an overlay function for a text field that shows some particular state depending on what’s the user input. When the user taps the text field, it will display an overlay that should show some information while the user is writing, much like a search bar on a web browser works.

My problem is that the content of the OverlayEntry doesn’t update at the same time the user input change, it only changes when I reopen the Overlay.

Expected behavior

enter image description here

Current behaviour

enter image description here

For testing this functionality, I’m just passing the same value from the text field to the overlay.

This is my code:

Home class

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
        child: Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  CustomTextField(),
                ]),
            // This trailing comma makes auto-formatting nicer for build methods.
          ),
        ));
  }
}

Custom Text field class:

class CustomTextField extends StatefulWidget {
  CustomTextField({Key? key}) : super(key: key);

  @override
  State<CustomTextField> createState() => _CustomTextFieldState();
}

class _CustomTextFieldState extends State<CustomTextField>
    with TickerProviderStateMixin {
  TextEditingController controlador = TextEditingController();
  FocusNode nodeUno = FocusNode();
  OverlayEntry? _overlayEntry;
  GlobalKey globalKey = GlobalKey();
  final LayerLink _layerLink = LayerLink();
  String inputText = '';

  @override
  void initState() {
    super.initState();
    OverlayState? overlayState = Overlay.of(context);
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      globalKey;
    });

    nodeUno.addListener(() {
      if (nodeUno.hasFocus) {
        _overlayEntry = _createOverlay(inputText);

        overlayState!.insert(_overlayEntry!);
      } else {
        _overlayEntry!.remove();
      }
    });
  }

  OverlayEntry _createOverlay([String? text]) {
    RenderBox renderBox = context.findRenderObject() as RenderBox;

    var size = renderBox.size;
    return OverlayEntry(
        builder: (context) => Positioned(
              width: size.width,
              child: CompositedTransformFollower(
                link: _layerLink,
                showWhenUnlinked: false,
                offset: Offset(0.0, size.height + 5.0),
                child: Material(
                  elevation: 5.0,
                  child: Column(
                    children: [
                      ListTile(
                        title: text != '' ? Text(text!) : const Text('data'),
                      ),
                    ],
                  ),
                ),
              ),
            ));
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 250,
      child: CompositedTransformTarget(
        link: _layerLink,
        child: TextField(
          focusNode: nodeUno,
          controller: controlador,
          onChanged: (value) {
            print(value);
            setState(() {
              inputText = value;
            });
          },
        ),
      ),
    );
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    Well, for what I could find, the only way to update the content of an overlay entry is to remove and insert again that entry, so this is what I found that works:

    class CustomTextField extends StatefulWidget {
      CustomTextField({Key? key}) : super(key: key);
    
      @override
      State<CustomTextField> createState() => _CustomTextFieldState();
    }
    
    class _CustomTextFieldState extends State<CustomTextField>
        with TickerProviderStateMixin {
      TextEditingController controlador = TextEditingController();
      FocusNode nodeUno = FocusNode();
      OverlayEntry? _overlayEntry;
      GlobalKey globalKey = GlobalKey();
      final LayerLink _layerLink = LayerLink();
      String inputText = '';
      OverlayState? overlayState;
      bool popoverOpened = false;
    
      @override
      void initState() {
        super.initState();
        _overlayEntry = null;
        overlayState = Overlay.of(context);
        WidgetsBinding.instance!.addPostFrameCallback((_) {
          globalKey;
        });
    
        nodeUno.addListener(() {
          if (nodeUno.hasFocus) {
            _overlayEntry = _createOverlay(inputText);
            overlayState!.insert(_overlayEntry!);
          } else {
            if (_overlayEntry != null) {
              _overlayEntry?.remove();
              _overlayEntry = null;
            }
          }
        });
      }
    
      OverlayEntry _createOverlay([String? text]) {
        RenderBox renderBox = context.findRenderObject() as RenderBox;
    
        if (_overlayEntry != null && popoverOpened) {
          _overlayEntry!.remove();
        }
        popoverOpened = true;
        var size = renderBox.size;
        return OverlayEntry(
            builder: (context) => Positioned(
                  width: size.width,
                  child: CompositedTransformFollower(
                    link: _layerLink,
                    showWhenUnlinked: false,
                    offset: Offset(0.0, size.height + 5.0),
                    child: OverlayClass(val: inputText),
                  ),
                ));
      }
    
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          width: 250,
          child: CompositedTransformTarget(
            link: _layerLink,
            child: TextField(
              focusNode: nodeUno,
              controller: controlador,
              onChanged: (value) {
                print(value);
                setState(() {
                  inputText = value;
                  _overlayEntry = _createOverlay(inputText);
                  overlayState!.insert(_overlayEntry!);
                });
              },
            ),
          ),
        );
      }
    }
    

  2. Just call this:

    _overlayEntry!.markNeedsBuild();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search