简体   繁体   中英

Cannot press buttons in my Flutter app until I press the Debug button in Dart DevTools while debugging

I'm writing a Flutter app and recently (I'm using the web-enabled version of flutter) I have to press the Debug button in the DartDev tool at the start of each debugging session to enable a button to work.For example, I press F5 on my keyboard to start the debugging session using the Mac iPhone Simulator as shown next:

按钮被禁用

I Command-Tab into the Dart DevTool that's automatically started in my browser when I stated the debugging session and press the little "debug" button:

按下调试按钮

Immediately in the Simulator, the "Sign In" button is enabled:

按钮已启用

This also happens when debugging on Android.

Any idea why this is? I can type into the text fields without pressing the debug button. I have no idea why the RaisedButton is disabled at the start. It might seem like a minor annoyance, but multiply it by the 100+ times a day you run the debugger during development and the frustration adds up.

Here is a main.dart file that reproduces this behavior:

import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Bleu Inventory",
      //debugShowCheckedModeBanner: false,
      home: LoginPage(), //MainPage(),
      theme: ThemeData(accentColor: Colors.white70),
    );
  }
}

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  bool _isLoading = false;
  String message = 'Please login to continue.';

  final TextEditingController emailController = new TextEditingController();
  final TextEditingController passwordController = new TextEditingController();
  FocusNode emailFocusNode;

  @override
  void initState() {
    super.initState();
    emailController.text = '';
    passwordController.text = '';
    emailFocusNode = FocusNode();
  }

  @override
  Widget build(BuildContext context) {
    // SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light
    //     .copyWith(statusBarColor: Colors.transparent));
    return Scaffold(
      body: Container(
        color: Colors.white,
        child: _isLoading
            ? Center(
                child: CircularProgressIndicator(
                backgroundColor: Colors.white,
              ))
            : ListView(
                children: <Widget>[
                  headerSection(),
                  textSection(),
                  buttonSection(),
                  messageSection(),
                ],
              ),
      ),
    );
  }

  signIn(String email, pass) async {
    Map data = {'USER': email, 'PASSWORD': pass};
    setState(() {
      _isLoading = false;
      message = 'logged in successfully';
    });
  }

  Container buttonSection() {
    return Container(
      // width: double.infinity, // MediaQuery.of(context).size.width,
      height: 40.0,
      padding: EdgeInsets.symmetric(horizontal: 15.0),
      margin: EdgeInsets.only(top: 15.0),
      child: RaisedButton(
        onPressed: emailController.text == "" || passwordController.text == ""
            ? null
            : () {
                // setState(() {
                _isLoading = true;
                // });
                signIn(emailController.text, passwordController.text);
              },
        elevation: 6.0,
        color: Colors.blue[800],
        child: Text("Sign In",
            style: TextStyle(
                color: Colors.white,
                fontSize: 18,
                fontWeight: FontWeight.bold)),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)),
      ),
    );
  }

  Container messageSection() {
    return Container(
      width: double.infinity,
      height: 40.0,
      padding: EdgeInsets.symmetric(horizontal: 15.0),
      margin: EdgeInsets.only(top: 15.0),
      child: Text(
        message,
        style: TextStyle(
          color: Colors.black,
          fontSize: 18,
          fontWeight: FontWeight.bold,
        ),
        textAlign: TextAlign.center,
      ),
    );
  }

  Container textSection() {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
      child: Column(
        children: <Widget>[
          TextFormField(
            autofocus: true,
            focusNode: emailFocusNode,
            controller: emailController,
            cursorColor: Colors.blue,
            style: TextStyle(color: Colors.blue[800]),
            decoration: InputDecoration(
              icon: Icon(Icons.email, color: Colors.blue[800]),
              hintText: "User Name",
              border: UnderlineInputBorder(
                  borderSide: BorderSide(color: Colors.grey)),
              hintStyle: TextStyle(color: Colors.grey),
            ),
          ),
          SizedBox(height: 30.0),
          TextFormField(
            controller: passwordController,
            cursorColor: Colors.black,
            obscureText: true,
            style: TextStyle(color: Colors.blue[800]),
            decoration: InputDecoration(
              icon: Icon(Icons.lock, color: Colors.blue[800]),
              hintText: "Password",
              border: UnderlineInputBorder(
                  borderSide: BorderSide(color: Colors.blue[800])),
              hintStyle: TextStyle(color: Colors.grey),
            ),
          ),
        ],
      ),
    );
  }

  Container headerSection() {
    return Container(
      margin: EdgeInsets.fromLTRB(20, 50.0, 20.0, 10.0),
      padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 30.0),
      child: Column(
        children: <Widget>[
          Text('My App Name',
              style: TextStyle(
                  color: Colors.blue[800],
                  fontSize: 32,
                  shadows: [
                    Shadow(
                        color: Colors.black,
                        blurRadius: 6,
                        offset: Offset.fromDirection(0))
                  ],
                  fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }
}

Here is the output of flutter -v doctor

~/flutter_workspace/debug_example 
   flutter -v doctor
[✓] Flutter (Channel dev, v1.13.5, on Mac OS X 10.14.6 18G2022, locale en-US)
    • Flutter version 1.13.5 at /Users/jdavies/development/flutter
    • Framework revision 41a911099b (7 days ago), 2019-12-19 13:48:02 -0800
    • Engine revision 0f90e6546b
    • Dart version 2.8.0 (build 2.8.0-dev.0.0 aa6709974d)


[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at /Users/jdavies/Library/Android/sdk
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-29, build-tools 29.0.2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.0)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.0, Build version 11A420a
    • CocoaPods version 1.7.5

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 3.5)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 42.1.1
    • Dart plugin version 191.8593
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)

[✓] VS Code (version 1.41.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.7.1

[✓] Connected device (3 available)
    • iPhone 11 Pro Max • A96AF9B1-AE6E-423E-B4C5-9ECD4E2D8DE3 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-13-0
      (simulator)
    • Chrome            • chrome                               • web-javascript • Google Chrome 79.0.3945.88
    • Web Server        • web-server                           • web-javascript • Flutter Tools

• No issues found!

Many thanks to JacksonZ for his insight and help with this. Using his suggestions, I have refactored the code into the following working solution:

import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Bleu Inventory",
      //debugShowCheckedModeBanner: false,
      home: LoginPage(), //MainPage(),
      theme: ThemeData(accentColor: Colors.white70),
    );
  }
}

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  /// Are we waiting on the login process?
  bool _isLoading = false;

  String message = 'Please login to continue.';
  static const _minUserNameLength = 5;
  static const _minPasswordLength = 8;

  /// This variable tells is if the name and password text fields were of a valid
  /// length (or not) before the current keystroke. This keeps us from calling
  /// setState() every time a keystroke is added beyond the minimum.
  bool wasNameAndPasswordValidBefore = false;

  final TextEditingController userNameController = new TextEditingController();
  final TextEditingController passwordController = new TextEditingController();
  FocusNode userNameFocusNode;

  @override
  void initState() {
    super.initState();
    userNameController.text = '';
    passwordController.text = '';
    userNameFocusNode = FocusNode();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.white,
        child: _isLoading
            ? Center(
                child: CircularProgressIndicator(
                backgroundColor: Colors.white,
              ))
            : ListView(
                children: <Widget>[
                  headerSection(),
                  textSection(),
                  buttonSection(),
                  messageSection(),
                ],
              ),
      ),
    );
  }

  signIn(String userName, pass) async {
    // Map data = {'USER': userName, 'PASSWORD': pass};
    setState(() {
      _isLoading = false;
      message = 'logged in successfully';
    });
  }

  Container buttonSection() {
    return Container(
      // width: double.infinity, // MediaQuery.of(context).size.width,
      height: 40.0,
      padding: EdgeInsets.symmetric(horizontal: 15.0),
      margin: EdgeInsets.only(top: 15.0),
      child: RaisedButton(
        onPressed: areNameAndPasswordSupplied() == false
            ? null
            : () {
                setState(() {
                  _isLoading = true;
                });
                signIn(userNameController.text, passwordController.text);
              },
        elevation: 6.0,
        color: Colors.blue[800],
        child: Text("Sign In",
            style: TextStyle(
                color: Colors.white,
                fontSize: 18,
                fontWeight: FontWeight.bold)),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)),
      ),
    );
  }

  Container messageSection() {
    return Container(
      width: double.infinity,
      height: 40.0,
      padding: EdgeInsets.symmetric(horizontal: 15.0),
      margin: EdgeInsets.only(top: 15.0),
      child: Text(
        message,
        style: TextStyle(
          color: Colors.black,
          fontSize: 18,
          fontWeight: FontWeight.bold,
        ),
        textAlign: TextAlign.center,
      ),
    );
  }

  /// Make changes to the UI IFF the name and password are supplied and of
  /// valid lengths and they were NOT both of valid lengths on the last keystroke.
  ///
  bool areNameAndPasswordSupplied() {
    // Are both fields valid at this moment?
    bool fieldsValid = userNameController.text.length >= _minUserNameLength &&
        passwordController.text.length >= _minPasswordLength;
    if (fieldsValid ^ wasNameAndPasswordValidBefore) {
      // The state has changed
      setState(() {});
    }
    wasNameAndPasswordValidBefore = fieldsValid; // Update our state of validity
    return wasNameAndPasswordValidBefore;
  }

  Container textSection() {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
      child: Column(
        children: <Widget>[
          TextFormField(
            autofocus: true,
            focusNode: userNameFocusNode,
            controller: userNameController,
            cursorColor: Colors.blue,
            style: TextStyle(color: Colors.blue[800]),
            decoration: InputDecoration(
              icon: Icon(Icons.face, color: Colors.blue[800]),
              hintText: "User Name",
              border: UnderlineInputBorder(
                  borderSide: BorderSide(color: Colors.grey)),
              hintStyle: TextStyle(color: Colors.grey),
            ),
            onChanged: (newText) {
              areNameAndPasswordSupplied();
            },
          ),
          SizedBox(height: 30.0),
          TextFormField(
            controller: passwordController,
            cursorColor: Colors.black,
            obscureText: true,
            style: TextStyle(color: Colors.blue[800]),
            decoration: InputDecoration(
              icon: Icon(Icons.lock, color: Colors.blue[800]),
              hintText: "Password",
              border: UnderlineInputBorder(
                  borderSide: BorderSide(color: Colors.blue[800])),
              hintStyle: TextStyle(color: Colors.grey),
            ),
            onChanged: (newText) {
              areNameAndPasswordSupplied();
            },
          ),
        ],
      ),
    );
  }

  Container headerSection() {
    return Container(
      margin: EdgeInsets.fromLTRB(20, 50.0, 20.0, 10.0),
      padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 30.0),
      child: Column(
        children: <Widget>[
          Text('My App Name',
              style: TextStyle(
                  color: Colors.blue[800],
                  fontSize: 32,
                  shadows: [
                    Shadow(
                        color: Colors.black,
                        blurRadius: 6,
                        offset: Offset.fromDirection(0))
                  ],
                  fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }
}

Button being disabled means the onPressed method of your RaisedButton is null . This happens when the app first evaluate your email and password controller. The reason why pressing debug will make it work is that pressing debug force the app the hot-reload and re-evaluate your onPressed method. Since now the value of email and password is no longer null , the onPressed is not null either, and it becomes available.

My suggestion is to, instead of evaluating your onPressed method in-place, use the onChange method of the textField (which is your email and password input) to check if email and password exist and do setState to set your handler onPressed from null to your real handler.

The root cause of your problem is that Flutter won't evaluate your condition unless you do a setState . However, you don't know when the input is going to change (as you are not using onChange in textField ) it will never be re-evaluated (of course until you do a debug).

I think you want to move logic inside your function and that way you can always have an onPressed method it just won't do isLoading and signIn unless email and password are supplied. I also added an empty else block to illustrate how always having a onPressed method allows you to do something if these fields are empty. You can remove it if you just want to do nothing.

onPressed: () {
  if (emailController.text != '' && passwordController.text != '') {
    setState(() {
      _isLoading = true;
    });
    signIn(userNameController.text, passwordController.text);
  } else {
    // Handle empty email and password, or just do nothing
  }
}

I also created a DartPad here with a working example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM