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

Flutter向Django发送OTP验证返回200,APP却触发SnackBar问题求助

OTP验证逻辑错误:服务器返回200仍触发SnackBar的解决方案

问题根源

  1. 状态变量作用域错误:statusCode、isLoading等变量定义在build方法内部,每次Widget重建时这些变量都会被重置为初始值,无法持久化存储请求结果。
  2. 异步逻辑顺序错误:点击按钮后调用sendOTP(异步方法),紧接着立刻判断statusCode,此时HTTP请求还未完成,statusCode仍为null,因此直接触发“check your connection”的SnackBar,后续请求返回200时的状态更新无法改变已经执行的判断逻辑。

修复方案

  1. 将状态变量移至_OTPScreenState类的成员位置,确保状态能在Widget生命周期内持久化。
  2. 在sendOTP的异步回调中处理请求结果的判断和UI跳转,确保等待请求完成后再执行后续逻辑。
  3. 增加加载状态控制,防止用户重复点击按钮。

修正后的完整代码

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:pinput/pinput.dart';
import 'package:whatsapp/Model/ChatModel.dart';
import 'package:whatsapp/Screens/home/HomeScreen.dart';
import 'package:whatsapp/common/utils/utils.dart';
import 'package:whatsapp/constants/colors.dart';
import 'package:whatsapp/utils/theme_manager.dart';

class OTPScreen extends StatefulWidget {
  const OTPScreen(
      {Key? key,
      required this.chatModels,
      required this.sourceChat,
      required this.phoneNumber})
      : super(key: key);
  final List<ChatModel> chatModels;
  final ChatModel sourceChat;
  final String phoneNumber;

  @override
  State<OTPScreen> createState() => _OTPScreenState();
}

class _OTPScreenState extends State<OTPScreen> {
  // 将状态变量移至类成员
  int? statusCode;
  bool isLoading = false;
  bool timeOut = false;
  final TextEditingController controller = TextEditingController();

  void sendOTP(String otp) async {
    setState(() {
      isLoading = true;
    });
    try {
      Response response = await post(
          Uri.parse("https://2858-151-240-216-221.ngrok-free.app/otp-api/"),
          body: {"otp": otp, "phone_number": widget.phoneNumber});
      print(response.body);
      setState(() {
        statusCode = response.statusCode;
      });
      // 请求完成后直接处理逻辑
      if (statusCode == 200) {
        if (mounted) { // 确保Widget仍在树中
          Navigator.pushReplacement(
            context,
            MaterialPageRoute(
              builder: (builder) => HomeScreen(
                chatModels: widget.chatModels,
                sourceChat: widget.sourceChat,
              ),
            ),
          );
        }
      } else {
        if (mounted) {
          showSnackBar(context: context, content: "try again");
        }
      }
    } catch (e) {
      print(e.toString());
      if (mounted) {
        showSnackBar(context: context, content: "check your connection");
      }
    } finally {
      setState(() {
        isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final defaultPinTheme = PinTheme(
      width: 56,
      height: 56,
      textStyle: TextStyle(
          fontSize: 20,
          color: Color.fromRGBO(30, 60, 87, 1),
          fontWeight: FontWeight.w600),
      decoration: BoxDecoration(
        border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
        borderRadius: BorderRadius.circular(20),
      ),
    );

    final focusedPinTheme = defaultPinTheme.copyDecorationWith(
      border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
      borderRadius: BorderRadius.circular(8),
    );

    final submittedPinTheme = defaultPinTheme.copyWith(
      decoration: defaultPinTheme.decoration?.copyWith(
        color: Color.fromRGBO(234, 239, 243, 1),
      ),
    );

    return Scaffold(
      backgroundColor:
          ThemeManeger().checkIsDark(context) ? primaryColor : Colors.white,
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        leading: IconButton(
          onPressed: () {
            Navigator.pop(context);
          },
          icon: Icon(
            Icons.arrow_back_ios_rounded,
            color: ThemeManeger().checkIsDark(context)
                ? Colors.white
                : Colors.black,
          ),
        ),
        elevation: 0,
      ),
      body: Container(
        margin: EdgeInsets.only(left: 25, right: 25),
        alignment: Alignment.center,
        child: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Image.asset(
                'assets/otp.png',
                width: 150,
                height: 150,
              ),
              SizedBox(
                height: 25,
              ),
              Text(
                "Phone Verification",
                style: TextStyle(
                    fontSize: 22,
                    fontWeight: FontWeight.bold,
                    color: ThemeManeger().checkIsDark(context)
                        ? Colors.white
                        : Colors.black),
              ),
              SizedBox(
                height: 10,
              ),
              Text(
                "We need to register your phone without getting started!",
                style: TextStyle(
                    fontSize: 16,
                    color: ThemeManeger().checkIsDark(context)
                        ? Colors.white
                        : Colors.black),
                textAlign: TextAlign.center,
              ),
              SizedBox(
                height: 30,
              ),
              Pinput(
                length: 6,
                controller: controller,
                showCursor: true,
                onCompleted: (pin) => print(pin),
              ),
              SizedBox(
                height: 20,
              ),
              SizedBox(
                width: double.infinity,
                height: 45,
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                      backgroundColor: tabColor,
                      shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(10))),
                  onPressed: isLoading ? null : () {
                    sendOTP(controller.text);
                  },
                  child: isLoading 
                      ? CircularProgressIndicator(color: Colors.white)
                      : const Text(
                          "Verify Phone Number",
                          style: TextStyle(),
                        ),
                ),
              ),
              Row(
                children: [
                  TextButton(
                      onPressed: () {
                        Navigator.pop(context);
                      },
                      child: Text(
                        "Edit Phone Number ?",
                        style: TextStyle(
                            color: ThemeManeger().checkIsDark(context)
                                ? Colors.white
                                : Colors.black),
                      ))
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

额外说明

  • 使用mounted判断确保Widget未被销毁时再执行上下文相关操作,避免内存泄漏或异常。
  • 加载状态isLoading在请求开始时设为true,结束时(finally块)设为false,同时禁用按钮并显示加载指示器,提升用户体验。

内容的提问来源于stack exchange,提问作者Amirhosein Pourmoshir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:07:02