Flutter向Django发送OTP验证返回200,APP却触发SnackBar问题求助
OTP验证逻辑错误:服务器返回200仍触发SnackBar的解决方案
问题根源
- 状态变量作用域错误:
statusCode、isLoading等变量定义在build方法内部,每次Widget重建时这些变量都会被重置为初始值,无法持久化存储请求结果。 - 异步逻辑顺序错误:点击按钮后调用
sendOTP(异步方法),紧接着立刻判断statusCode,此时HTTP请求还未完成,statusCode仍为null,因此直接触发“check your connection”的SnackBar,后续请求返回200时的状态更新无法改变已经执行的判断逻辑。
修复方案
- 将状态变量移至
_OTPScreenState类的成员位置,确保状态能在Widget生命周期内持久化。 - 在
sendOTP的异步回调中处理请求结果的判断和UI跳转,确保等待请求完成后再执行后续逻辑。 - 增加加载状态控制,防止用户重复点击按钮。
修正后的完整代码
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
相关产品推荐
相关产品推荐

