【问题标题】:Cannot press buttons in my Flutter app until I press the Debug button in Dart DevTools while debugging在调试时按下 Dart DevTools 中的调试按钮之前,无法按下 Flutter 应用程序中的按钮
【发布时间】:2020-04-15 16:29:15
【问题描述】:

我正在编写一个 Flutter 应用程序,最近(我正在使用支持 Web 的 Flutter 版本)我必须在每个调试会话开始时按下 DartDev 工具中的 Debug 按钮才能使按钮工作。例如,我按下键盘上的 F5 以使用 Mac iPhone 模拟器启动调试会话,如下所示:

当我陈述调试会话并按下小“调试”按钮时,我使用 Command-Tab 进入浏览器中自动启动的 Dart DevTool:

立即在模拟器中启用“登录”按钮:

在 Android 上调试时也会发生这种情况。

知道这是为什么吗?我可以在不按调试按钮的情况下输入文本字段。我不知道为什么 RaisedButton 在开始时被禁用。这似乎是一个小烦恼,但将其乘以您在开发期间每天运行调试器的 100 多次,挫败感就会加起来。

这是一个重现此行为的 main.dart 文件:

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

这是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!

非常感谢 JacksonZ 在这方面的洞察力和帮助。根据他的建议,我将代码重构为以下工作解决方案:

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

【问题讨论】:

  • 请添加一些代码

标签: android ios flutter dart


【解决方案1】:

按钮被禁用意味着您的RaisedButtononPressed 方法是null。当应用程序首次评估您的电子邮件和密码控制器时,就会发生这种情况。按下 debug 会使其工作的原因是按下 debug 会强制应用程序热重新加载并重新评估您的 onPressed 方法。由于现在email和password的值不再是nullonPressed也不再是null,就可以使用了。

我的建议是,不要就地评估您的onPressed 方法,而是使用textField(这是您的电子邮件和密码输入)的onChange 方法来检查电子邮件和密码是否存在并执行@ 987654331@ 将您的处理程序 onPressed 从 null 设置为您的真实处理程序。

您的问题的根本原因是除非您执行setState,否则 Flutter 不会评估您的状况。但是,您不知道输入何时会更改(因为您没有在 textField 中使用 onChange)它永远不会被重新评估(当然,直到您进行调试)。

【讨论】:

  • 顺便说一句,_isLoading = true; 周围的setState 可能是需要的。如果您打算在进行后端身份验证 API 调用时显示加载屏幕,则可以考虑将其添加回来。
  • 哇,你不仅回答了我的问题,还让我了解了更多关于 Flutter 的知识!你的想法很完美。对于我的 Flutter 新手,我正在发布更新的代码。
【解决方案2】:

我认为你想在你的函数中移动逻辑,这样你总是可以有一个 onPressed 方法它不会做 isLoadingsignIn 除非 emailpassword 被提供。我还添加了一个空的else 块来说明始终拥有onPressed 方法如何允许您在这些字段为空时执行某些操作。如果您只想什么都不做,可以将其删除。

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

我还通过一个工作示例创建了 DartPad here

【讨论】:

    猜你喜欢
    • 2016-12-31
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 2020-04-13
    • 2021-12-26
    • 2011-06-14
    • 1970-01-01
    • 2013-02-11
    相关资源
    最近更新 更多