flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
162.21k stars 26.64k forks source link

Cursor disappears after TextField receives focus and executes setState. #106767

Open caduandrade opened 1 year ago

caduandrade commented 1 year ago

I'm just updating the background after my TextField receives focus.

The TextField lost the cursor after a Container receives a color in the setState. Only happens if the color was NULL.

The setState without set the color is ok.

Steps to Reproduce

  1. Select the TextField
  2. No cursor

Code

import 'dart:math' as math;
import 'package:flutter/material.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Focus',
      home: HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final FocusNode _focusNode = FocusNode();
  final TextEditingController _controller = TextEditingController();
  Color? _color;

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(_onFocusChanged);
  }

  void _onFocusChanged() {
    // Here you can see that the TextField will show the cursor but will lost
    // after setState
    Future.delayed(
        const Duration(seconds: 2),
        () => setState(() {
              print('focus changed: ${_focusNode.hasFocus}');

              _color = _focusNode.hasFocus ? Colors.green[200] : null;

              // No problem after keeping any color
              // _color=_randomColor();
            }));
  }

  Color _randomColor() {
    return Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
        .withOpacity(1);
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            // setState + color = TextField cursor gone
            color: _color,
            child: Center(
                child: SizedBox(
                    width: 150,
                    child: TextField(
                        focusNode: _focusNode, controller: _controller)))));
  }
}

flutter doctor

cadu@cadulandia:~$ flutter doctor -v
[✓] Flutter (Channel stable, 3.0.2, on Ubuntu 20.04.4 LTS 5.13.0-51-generic, locale en_US.UTF-8)
    • Flutter version 3.0.2 at /cadu/desenv/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision cd41fdd495 (3 weeks ago), 2022-06-08 09:52:13 -0700
    • Engine revision f15f824b57
    • Dart version 2.17.3
    • DevTools version 2.12.2

[✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
    • Android SDK at /home/cadu/Android/Sdk
    • Platform android-32, build-tools 32.0.0
    • ANDROID_SDK_ROOT = /home/cadu/Android/Sdk
    • Java binary at: /cadu/desenv/android-studio/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Chrome - develop for the web
    • Chrome at google-chrome

[✓] Linux toolchain - develop for Linux desktop
    • clang version 10.0.0-4ubuntu1
    • cmake version 3.16.3
    • ninja version 1.10.0
    • pkg-config version 0.29.1

[✓] Android Studio (version 2021.2)
    • Android Studio at /cadu/desenv/android-studio
    • Flutter plugin version 67.1.2
    • Dart plugin version 212.5744
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] IntelliJ IDEA Community Edition (version 2021.2)
    • IntelliJ at /cadu/desenv/idea-IC-211.7628.21
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.68.1)
    • VS Code at /usr/share/code
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (2 available)
    • Linux (desktop) • linux  • linux-x64      • Ubuntu 20.04.4 LTS 5.13.0-51-generic
    • Chrome (web)    • chrome • web-javascript • Google Chrome 103.0.5060.53

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!
huycozy commented 1 year ago

Hi @caduandrade, thanks for filing the issue. I think this is working as intended. You are wrapping TextField by a Container which has color changing by invoking setState. It looks like you click outside TextField to unfocus it.

Please look at my below sample to see this more clearly:

Sample code ```dart import 'dart:math' as math; import 'package:flutter/material.dart'; void main() { runApp(const ExampleApp()); } class ExampleApp extends StatelessWidget { const ExampleApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Focus', home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State createState() => _HomePageState(); } class _HomePageState extends State { final FocusNode _focusNode = FocusNode(); final TextEditingController _controller = TextEditingController(); Color? _color; @override void initState() { super.initState(); _focusNode.addListener(_onFocusChanged); } void _onFocusChanged() { // Here you can see that the TextField will show the cursor but will lost // after setState Future.delayed(const Duration(seconds: 2), () { setState(() { print('focus changed: ${_focusNode.hasFocus}'); _color = _focusNode.hasFocus ? Colors.green[200] : null; // No problem after keeping any color // _color=_randomColor(); }); }); } Color _randomColor() { return Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1); } @override void dispose() { _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { setState(() {}); }, tooltip: 'Increment', child: const Icon(Icons.add), ), body: Center( child: SizedBox( width: 150, child: Column( children: [ Container( width: 50, height: 50, color: _color, ), TextField(focusNode: _focusNode, controller: _controller), ], ), ), ), ); } } ```
Demo https://user-images.githubusercontent.com/104349824/176374184-752bb203-502f-43fb-afe1-171285be819f.mp4

Do you agree with above explanation?

caduandrade commented 1 year ago

Hi @huycozy !

Unfortunately, I do not agree. :grimacing:

I used the delay just to show that the cursor was visible before disappearing.

I understood the problem because I'm a developer, but it would still be a bug for a user.

The problem occurs by clicking on TextField. The first and only click is on the TextField and it doesn't show the cursor.

The same problem occurs when typing in the TextField.

Note that in both cases the user did not click away.

Maybe it's difficult to solve with the current architecture but as I said, for the user this would be a bug. I'm a developer and it took me a while to figure out that the problem was setting the color.

Thank you very much!

Clicking on TextField

import 'package:flutter/material.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Clicking',
      home: HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final FocusNode _focusNode = FocusNode();
  final TextEditingController _controller = TextEditingController();
  Color? _color;

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(_onFocusChanged);
  }

  void _onFocusChanged() {
    setState(() {
      print('focus changed: ${_focusNode.hasFocus}');

      _color = _focusNode.hasFocus ? Colors.green[200] : null;
    });
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            // setState + color = TextField cursor gone
            color: _color,
            child: Center(
                child: SizedBox(
                    width: 150,
                    child: TextField(
                        focusNode: _focusNode, controller: _controller)))));
  }
}

Typing on TextField

import 'package:flutter/material.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Typing',
      home: 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();
  Color? _color;

  @override
  void initState() {
    super.initState();
    _controller.addListener(_onType);
  }

  void _onType() {
    Color? color = _controller.text.isNotEmpty ? Colors.green[200] : null;
    if (_color != color) {
      setState(() {
        _color = color;
      });
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            // setState + color = TextField cursor gone
            color: _color,
            child: Center(
                child: SizedBox(
                    width: 150, child: TextField(controller: _controller)))));
  }
}
maheshmnj commented 1 year ago

Hi @caduandrade, Thanks for filing the issue. That is really a strange behavior, unfortunately, I do not have a reasoning for that. I am labeling this issue for further insights from the team. The issue is reproducible with the above code samples. With the first code sample the textfield has focus but the cursor is not visible.

flutter doctor -v (mac) ``` [✓] Flutter (Channel stable, 3.0.3, on macOS 12.4 21F79 darwin-arm, locale en-IN) • Flutter version 3.0.3 at /Users/mahesh/Documents/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 676cefaaff (5 days ago), 2022-06-22 11:34:49 -0700 • Engine revision ffe7b86a1e • Dart version 2.17.5 • DevTools version 2.12.2 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc4) • Android SDK at /Users/mahesh/Library/Android/sdk • Platform android-32, build-tools 33.0.0-rc4 • ANDROID_HOME = /Users/mahesh/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1) • Xcode at /Applications/Xcode.app/Contents/Developer • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2021.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) [✓] IntelliJ IDEA Community Edition (version 2021.2.1) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin version 61.2.4 • Dart plugin version 212.5080.8 [✓] VS Code (version 1.67.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.43.20220621 [✓] Connected device (3 available) • sdk gphone arm64 (mobile) • emulator-5554 • android-arm64 • Android 11 (API 30) (emulator) • macOS (desktop) • macos • darwin-arm64 • macOS 12.4 21F79 darwin-arm • Chrome (web) • chrome • web-javascript • Google Chrome 103.0.5060.53 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found! ``` ``` [✓] Flutter (Channel master, 3.1.0-0.0.pre.1376, on macOS 12.4 21F79 darwin-arm, locale en-IN) • Flutter version 3.1.0-0.0.pre.1376 on channel master at /Users/mahesh/Documents/flutter_master • Upstream repository https://github.com/flutter/flutter.git • Framework revision 3598f20002 (9 hours ago), 2022-06-26 18:19:04 -0400 • Engine revision ee56813c14 • Dart version 2.18.0 (build 2.18.0-228.0.dev) • DevTools version 2.14.1 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc4) • Android SDK at /Users/mahesh/Library/Android/sdk • Platform android-32, build-tools 33.0.0-rc4 • ANDROID_HOME = /Users/mahesh/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 13C100 • CocoaPods version 1.11.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2021.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) [✓] IntelliJ IDEA Community Edition (version 2021.2.1) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin version 61.2.4 • Dart plugin version 212.5080.8 [✓] VS Code (version 1.67.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.43.20220621 [✓] Connected device (3 available) • sdk gphone arm64 (mobile) • emulator-5554 • android-arm64 • Android 11 (API 30) (emulator) • macOS (desktop) • macos • darwin-arm64 • macOS 12.4 21F79 darwin-arm • Chrome (web) • chrome • web-javascript • Google Chrome 103.0.5060.53 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found! ```
justinmc commented 1 year ago

This is really interesting, I'm guessing that the setState causes the render object to update and then the field to lose focus somehow.

I noticed that there's no bug if the color change happens to a sibling widget.

Example with no bug when sibling rebuilds ```dart import 'package:flutter/material.dart'; void main() { runApp(const ExampleApp()); } class ExampleApp extends StatelessWidget { const ExampleApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, title: 'Clicking', home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State createState() => _HomePageState(); } class _HomePageState extends State { final FocusNode _focusNode = FocusNode(); final TextEditingController _controller = TextEditingController(); Color? _color; @override void initState() { super.initState(); _focusNode.addListener(_onFocusChanged); } void _onFocusChanged() { setState(() { print('focus changed: ${_focusNode.hasFocus}'); _color = _focusNode.hasFocus ? Colors.green[200] : null; }); } @override void dispose() { _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( children: [ Container( width: 100.0, height: 100.0, color: _color, ), TextField( focusNode: _focusNode, controller: _controller, ), ], ), ), ); } } ```
haggaikaunda commented 1 year ago

I ran into a similar issue with a disappearing cursor when I found this post. In my case I had a decoration property I conditionally set to a value when the TextField was focused.

For this particular case, a work around is to use the lifecycle methods onTap and onSubmitted to conditionally set the color, tapping the text field will focus it and submitting it will do the opposite.

import 'package:flutter/material.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Clicking',
      home: HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final FocusNode _focusNode = FocusNode();
  final TextEditingController _controller = TextEditingController();
  Color? _color;

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        // setState + color = TextField cursor gone
        color: _color,
        child: Center(
          child: SizedBox(
            width: 150,
            child: TextField(
              onTap: () => setState(() {
                _color = Colors.green[200];
              }),
              onSubmitted: (value) => setState(() {
                _color = Colors.white10;
              }),
              focusNode: _focusNode,
              controller: _controller,
            ),
          ),
        ),
      ),
    );
  }
}
caduandrade commented 1 year ago

A workaround is to replace the null color with Colors.transparent.

flutter-triage-bot[bot] commented 10 months ago

This issue is assigned but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

xu-baolin commented 10 months ago

I will fix this soon.

osh91 commented 6 months ago

I am encountering a similar issue with the TextInput cursor, and I believe it may be a bug. I have a TextInput designed for password entry.

In my first attempt, when I input an incorrect password and press enter, I observe that the TextField controller clears, and the cursor remains visible.

However, in the second attempt, when I enter an incorrect password again and press enter, the TextField controller clears, but the cursor disappears.

This issue persists whether or not I use a FocusNode.

flutter-triage-bot[bot] commented 5 months ago

This issue is assigned to @xu-baolin but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

apartyagi commented 2 weeks ago

@xu-baolin , please fix this issue or inform the community about it so that other people can contribute