You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter实现TextButton点击后API请求时显示加载状态

实现登录按钮加载状态及跳转功能

修改步骤:

  • 添加_isLoading状态变量控制加载状态
  • 根据加载状态切换按钮显示内容(加载时显示CircularProgressIndicator)
  • 请求前后更新加载状态,避免重复点击

修改后的完整代码:

import "dart:convert";
import 'package:http/http.dart' as http;
import 'register.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(const MaterialApp(
      home: LoginScreen(),
    ));

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

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  bool passwordVisible = false;
  late String email;
  late String password1;
  var md5password = "";
  late SharedPreferences prefs;
  bool _isLoading = false; // 新增:控制加载状态

  @override
  void initState() {
    super.initState();
    initSharedPref();
    passwordVisible = true;
  }

  void initSharedPref() async {
    prefs = await SharedPreferences.getInstance();
  }

  String generateMd5(String input) {
    return md5.convert(utf8.encode(input)).toString();
  }

  GlobalKey<FormState> formKey = GlobalKey<FormState>();

  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    super.dispose();
    emailController.dispose();
    passwordController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    double height = MediaQuery.of(context).size.height;
    double width = MediaQuery.of(context).size.width;
    return WillPopScope(
      onWillPop: () async {
        return false;
      },
      child: Scaffold(
        backgroundColor: Colors.white,
        body: SingleChildScrollView(
          child: Form(
            key: formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  height: height * 0.30,
                  width: double.infinity,
                  decoration: const BoxDecoration(color: Colors.white),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      Image.asset(
                        "assets/title.png",
                        width: 350.0,
                      ),
                      const SizedBox(
                        height: 30,
                      ),
                    ],
                  ),
                ),
                const SizedBox(
                  height: 50,
                ),
                const Center(
                  child: Text('Login',
                      style: TextStyle(
                          fontSize: 30.0,
                          fontWeight: FontWeight.bold,
                          color: Colors.black)),
                ),
                const SizedBox(height: 30),
                Center(
                  child: SizedBox(
                    height: height * 0.08,
                    width: width - 35,
                    child: TextFormField(
                      controller: emailController,
                      validator: (email) {
                        if (email == null || email.isEmpty) {
                          return "Please enter Email";
                        } else if (!email.contains("@") ||
                            !email.contains(".")) {
                          return "Please enter a valid email address";
                        }
                        return null;
                      },
                      onChanged: (value) {
                        setState(() {
                          email = value;
                        });
                      },
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(color: Colors.black)),
                        focusedBorder: OutlineInputBorder(
                          borderSide: BorderSide(color: Color(0xFF006ff6)),
                        ),
                        prefixIcon: Icon(Icons.email, color: Color(0xFF006ff6)),
                        labelText: "Email",
                        labelStyle:
                            TextStyle(color: Colors.grey, fontSize: 13.0),
                      ),
                    ),
                  ),
                ),
                const SizedBox(height: 30),
                Center(
                  child: SizedBox(
                    height: height * 0.08,
                    width: width - 35,
                    child: TextFormField(
                      controller: passwordController,
                      validator: (password) {
                        if (password == null || password.isEmpty) {
                          return "Please enter your password";
                        }
                        return null;
                      },
                      onChanged: (value) {
                        setState(() {
                          password1 = value;
                        });
                        md5password = generateMd5(password1);
                        print(md5password);
                      },
                      obscureText: passwordVisible,
                      obscuringCharacter: "*",
                      decoration: InputDecoration(
                        border: const OutlineInputBorder(),
                        enabledBorder: const OutlineInputBorder(
                            borderSide: BorderSide(color: Colors.black)),
                        focusedBorder: const OutlineInputBorder(
                          borderSide: BorderSide(color: Color(0xFF006ff6)),
                        ),
                        prefixIcon: const Icon(Icons.lock_open,
                            color: Color(0xFF006ff6)),
                        labelText: "Password",
                        labelStyle:
                            const TextStyle(color: Colors.grey, fontSize: 13.0),
                        suffixIcon: IconButton(
                          icon: Icon(passwordVisible
                              ? Icons.visibility
                              : Icons.visibility_off),
                          onPressed: () {
                            setState(() {
                              passwordVisible = !passwordVisible;
                            });
                          },
                        ),
                      ),
                    ),
                  ),
                ),
                Align(
                  alignment: Alignment.centerRight,
                  child: TextButton(
                    onPressed: () {
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => const ForgotPass()));
                    },
                    child: const Text(
                      "Forgot your Password ?",
                      style: TextStyle(
                          color: Colors.grey,
                          decoration: TextDecoration.underline),
                    ),
                  ),
                ),
                const SizedBox(height: 20),
                Center(
                  child: SizedBox(
                    height: height * 0.08,
                    width: width - 35,
                    child: TextButton(
                      onPressed: _isLoading
                          ? null // 加载时禁用按钮
                          : () async {
                              if (formKey.currentState!.validate()) {
                                print(email);
                                print(md5password);
                                await loginUser();
                              }
                            },
                      style: TextButton.styleFrom(
                          backgroundColor: const Color(0xFF006ff6),
                          shadowColor: Colors.black,
                          elevation: 5,
                          shape: const RoundedRectangleBorder(
                              borderRadius:
                                  BorderRadius.all(Radius.circular(10)))),
                      child: _isLoading
                          ? const CircularProgressIndicator(
                              color: Colors.white,
                            )
                          : const Text(
                              "SIGN IN",
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 20.0,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                    ),
                  ),
                ),
                const SizedBox(
                  height: 20.0,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Text(
                      "Don't have an account?",
                      style: TextStyle(color: Colors.grey),
                    ),
                    TextButton(
                        onPressed: () {
                          Navigator.push(
                              context,
                              MaterialPageRoute(
                                  builder: (context) => const Register()));
                        },
                        child: const Text(
                          "Sign up",
                          style: TextStyle(color: Colors.black),
                        ))
                  ],
                )
              ],
            ),
          ),
        ),
      ),
    );
  }

  Future<void> loginUser() async {
    setState(() {
      _isLoading = true; // 请求开始,设置加载状态
    });

    try {
      if (emailController.text.isNotEmpty &&
          passwordController.text.isNotEmpty) {
        var reqBody = {
          "email": emailController.text,
          "password": generateMd5(passwordController.text)
        };

        var response = await http.post(
            Uri.parse("https://testing.com/user/login"),
            headers: {"Content-Type": "application/json"},
            body: jsonEncode(reqBody));

        if (response.statusCode == 200) {
          var jsonResponse = jsonDecode(response.body);
          if (jsonResponse['success']) {
            var myToken = jsonResponse['data']['token'];
            await prefs.setString('token', myToken);
            print(myToken);
            if (mounted) { // 确保widget未被销毁再导航
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => Dashboard(token: myToken)));
            }
          } else {
            print('Something went wrong');
          }
          print(response.statusCode);
        } else if (response.statusCode == 400) {
          if (mounted) {
            const snackBar = SnackBar(
              content: Text('Incorrect Username or Password'),
              backgroundColor: Colors.red,
              elevation: 10,
              behavior: SnackBarBehavior.floating,
              margin: EdgeInsets.all(5),
            );
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          }
        } else if (response.statusCode == 500) {
          if (mounted) {
            const snackBar = SnackBar(
              content: Text('Server Error'),
              backgroundColor: Colors.red,
              elevation: 10,
              behavior: SnackBarBehavior.floating,
              margin: EdgeInsets.all(5),
            );
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          }
        } else {
          if (mounted) {
            const snackBar = SnackBar(
              content: Text('Something went wrong'),
              backgroundColor: Colors.red,
              elevation: 10,
              behavior: SnackBarBehavior.floating,
              margin: EdgeInsets.all(5),
            );
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          }
        }
      }
    } catch (e) {
      print("Error: $e");
      if (mounted) {
        const snackBar = SnackBar(
          content: Text('Network error occurred'),
          backgroundColor: Colors.red,
          elevation: 10,
          behavior: SnackBarBehavior.floating,
          margin: EdgeInsets.all(5),
        );
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      }
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false; // 请求结束,取消加载状态
        });
      }
    }
  }
}

// 补充原代码缺失的类定义
class ForgotPass extends StatelessWidget {
  const ForgotPass({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Forgot Password")),
    );
  }
}

class Dashboard extends StatelessWidget {
  final String token;
  const Dashboard({Key? key, required this.token}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Dashboard")),
      body: Center(child: Text("Token: $token")),
    );
  }
}

关键修改说明:

  1. 加载状态控制:新增_isLoading变量标记请求状态,加载时禁用按钮避免重复提交
  2. UI动态切换:按钮根据_isLoading状态显示加载指示器或登录文本
  3. 状态安全更新:在finally块中统一恢复加载状态,确保无论请求成功/失败/异常都能重置UI
  4. 安全导航:使用mounted检查widget存活状态,避免异步操作后导航报错
  5. 代码修复:补充原代码缺失的导入、未定义变量及类定义,保证代码可运行

内容的提问来源于stack exchange,提问作者Sakthivel k

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 04:02:35