Flutter中无网络连接时如何循环显示AlertDialog?
问题描述
我需要实现以下功能:
- 设备无网络连接时,显示提示「无网络连接,请检查网络后重试」的AlertDialog
- 点击「Retry」按钮后,若仍无网络则持续显示该对话框;一旦网络恢复,自动关闭对话框并继续执行登录流程
我尝试在重试逻辑中使用Connectivity().checkConnectivity()和ConnectivityResult.none,但未达到预期效果。我的代码如下:
import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:namer_app/Passenger_Widgets/GuestRegistration.dart'; import 'package:namer_app/login_auth/login_method.dart'; import 'package:namer_app/login_auth/resetpass_method.dart'; import 'package:namer_app/Passenger_Widgets/ScheduleWindow.dart'; import 'package:namer_app/prf_login/my_buttons.dart'; import 'package:namer_app/prf_login/my_textfields.dart'; import 'package:namer_app/prf_login/snackbar_ext.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Supabase.initialize( url: 'https://', anonKey: '0000', ); final prefs = await SharedPreferences.getInstance(); final bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false; List<ConnectivityResult> connectivityResults = await (Connectivity().checkConnectivity()); ConnectivityResult connectivityResult = connectivityResults.first; runApp( MaterialApp( home: MyApp(connectivityResult, prefs, isLoggedIn), ), ); } class MyApp extends StatelessWidget { final ConnectivityResult connectivityResult; final SharedPreferences prefs; final bool isLoggedIn; MyApp(this.connectivityResult, this.prefs, this.isLoggedIn); @override Widget build(BuildContext context) { if (connectivityResult == ConnectivityResult.none) { // Show a popup to inform the user that there's no internet connection WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('No Internet Connection'), content: Text('Please check your internet connection and try again.'), actions: [ ElevatedButton( child: Text('Retry'), onPressed: () => Navigator.pop(context), ), ], ), ); }); } return MaterialApp( theme: ThemeData( appBarTheme: AppBarTheme( iconTheme: IconThemeData( color: Colors.white, ), // Set back button color to white ), ), home: isLoggedIn ? ScheduleWindow(prefs: prefs) : LOGIN_passenger(), ); } } class LOGIN_passenger extends StatefulWidget { LOGIN_passenger({super.key}); @override State<LOGIN_passenger> createState() => _LOGIN_passengerState(); } class _LOGIN_passengerState extends State<LOGIN_passenger> { GlobalKey<FormState> _formkey = GlobalKey(); bool _isLoading = false; late TextEditingController emailController; late TextEditingController passController; //LOGIN ATTEMPTS TIMER int _loginAttempts = 0; bool _isCooldown = false; Timer? _cooldownTimer; int _cooldownTime = 60; // Cooldown time in seconds @override void initState() { super.initState(); emailController = TextEditingController(); passController = TextEditingController(); } @override void dispose() { emailController.dispose(); passController.dispose(); _cooldownTimer?.cancel(); super.dispose(); } bool isValidEmail(String input) { final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+'); return emailRegex.hasMatch(input); } bool isValidPhoneNumber(String input) { final phoneWithPrefix = '63$input'; final phoneRegex = RegExp(r'^63\d{10}$'); return phoneRegex.hasMatch(phoneWithPrefix); } void insertPrefs(AuthResponse response) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); if (response.user != null) { final user = response.user?.id; final selectQuery = await Supabase.instance.client .from('registered_passenger') .select() .eq('user_id', user.toString()); if (selectQuery.isNotEmpty) { prefs.setBool('isLoggedIn', true); prefs.setString('userId', selectQuery[0]['user_id']); prefs.setString('lastName', selectQuery[0]['last_name']); prefs.setString('firstName', selectQuery[0]['first_name']); prefs.setString('middleInitial', selectQuery[0]['middle_initial']); prefs.setString('email', selectQuery[0]['email']); prefs.setString('password', selectQuery[0]['password']); prefs.setString('profession', selectQuery[0]['profession']); String contactNumber = selectQuery[0]['phone']; prefs.setString('contactNumber', contactNumber.substring(2)); prefs.setString('age', selectQuery[0]['age']); prefs.setString('gender', selectQuery[0]['gender']); prefs.setString('region', selectQuery[0]['region']); prefs.setString('province', selectQuery[0]['province']); prefs.setString('municipality', selectQuery[0]['municipality']); prefs.setString('barangay', selectQuery[0]['barangay']); prefs.setString('titlePage', 'Schedule'); navigatetoSched(prefs); } } } void _login() async { final connectivityResult = await (Connectivity().checkConnectivity()); if (connectivityResult == ConnectivityResult.none) { context.showSnackbar( message: 'No internet connection. Please check your internet connection and try again.', backgroundColor: Colors.red, ); setState(() { _isLoading = false; // Reset _isLoading so the button can be clicked again }); return; } if (_isCooldown) { context.showSnackbar( message: 'Too many attempts. Please wait $_cooldownTime seconds.', backgroundColor: Colors.red, ); return; } final isValid = _formkey.currentState?.validate(); if (isValid != null && isValid) { setState(() { _isLoading = true; }); try { final input = emailController.text; final password = passController.text; final response; if (isValidEmail(input)) { response = await Supabase.instance.client.auth.signInWithPassword( email: input, password: password, ); insertPrefs(response); } else if (isValidPhoneNumber(input)) { response = await Supabase.instance.client.auth.signInWithPassword( phone: '63$input', password: password, ); insertPrefs(response); } else { throw AuthException('Invalid email or phone number'); } setState(() { _isLoading = false; }); context.showSnackbar( message: 'LOGIN SUCCESSFUL', backgroundColor: Colors.green, ); } on AuthException catch (e) { if (e.message.contains('SocketException')) { context.showSnackbar( message: 'No internet connection. Please check your internet connection and try again.', backgroundColor: Colors.red, ); } else { context.showSnackbar( message: e.message, backgroundColor: Colors.red, ); } setState(() { _isLoading = false; // Reset _isLoading in case of an error }); } catch (e) { context.showSnackbar( message: e.toString(), backgroundColor: Colors.red, ); setState(() { _isLoading = false; // Reset _isLoading for other types of errors }); } } } /*Future<void> _logout() async { if (selectedFerry != null) { await _supabaseClient .from('boats') .update({'is_active': false}) // Set ferry as inactive .eq('boat_name', selectedFerry!) } // Continue with logout process }*/ void _handleLoginFailure(String errorMessage) { context.showSnackbar(message: errorMessage, backgroundColor: Colors.red); setState(() { _isLoading = false; _loginAttempts++; }); if (_loginAttempts >= 3) { _startCooldown(); } } void _startCooldown() { setState(() { _isCooldown = true; }); _cooldownTimer = Timer.periodic(Duration(seconds: 1), (timer) { setState(() { _cooldownTime--; }); if (_cooldownTime == 0) { timer.cancel(); setState(() { _isCooldown = false; _loginAttempts = 0; _cooldownTime = 60; // Reset cooldown time }); } }); } void navigatetoSched(SharedPreferences prefs) { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => ScheduleWindow(prefs: prefs)), (route) => false); } void _forgotPassword() { Navigator.push( context, MaterialPageRoute(builder: (_) => ResetpassMethod()), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( preferredSize: Size.fromHeight(270.0), child: AppBar( centerTitle: true, flexibleSpace: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('images/bgprf1.png'), fit: BoxFit.fill, ), ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'images/prf_logo.png', width: 155.0, height: 155.0, ), SizedBox(height: 10.0), Text( 'PASIG RIVER FERRY PORTAL', style: TextStyle( color: Color(0xFF33385F), fontSize: 20, fontWeight: FontWeight.bold, ), ), ], ), ), ), ), ), backgroundColor: Colors.white, body: Container( padding: EdgeInsets.all(20.0), child: SingleChildScrollView( child: Form( key: _formkey, // Associate the Form with the GlobalKey child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox(height: 30.0), Text( 'WELCOME', style: TextStyle( color: Color(0xFF33385F), fontSize: 30, fontWeight: FontWeight.bold, ), ), Text( 'Please, Login to your account!', style: TextStyle( color: Color(0xFF666666), fontSize: 15, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 20.0), // Textfield for Email/Username Textfield_auth( controller: emailController, prefixIcon: Icon(Icons.person), hintText: 'Enter Email/Username', labelText: 'Email/Username', obscureText: false, ), const SizedBox(height: 15.0), // Textfield for Password Textfield_authpass( controller: passController, prefixIcon: Icon(Icons.lock), hintText: 'Enter Password', labelText: 'Password', ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: _forgotPassword, child: Text( 'Forgot Password?', style: TextStyle( color: Color(0xFF666666), fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), SizedBox(height: 30.0), LoginButton( onTap: _isLoading || _isCooldown ? null : _login, ), if (_isCooldown) Center( child: Text( 'Please wait $_cooldownTime seconds to try again.', style: TextStyle( color: Colors.red, fontSize: 16, ), ), ), const SizedBox(height: 20.0), // Continue with Google account Padding( padding: const EdgeInsets.symmetric(horizontal: 5.0), child: Row( children: [ Expanded( child: Divider( thickness: 1, color: Colors.grey, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: Text( 'OR', style: TextStyle(color: Colors.grey), ), ), Expanded( child: Divider( thickness: 1, color: Colors.grey, ), ), ], ), ), SizedBox(height: 30.0), // GOOGLE SIGN IN Center( child: Row( mainAxisSize: MainAxisSize.max, children: [ const SizedBox(height: 5), GuestButton( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => PrfGuestRegistration(), ), ); }, ), SignUpButton( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ChooseLoginMethod(), ), ); }, ), ], ), ), ], ), ), ), ), ); } }
解决方案
核心问题是你当前仅做了单次网络状态检查,没有持续监听网络变化,且点击Retry时直接关闭弹窗,未验证网络是否恢复。以下是针对性修改:
1. 将MyApp改为StatefulWidget,实现网络状态监听
把原来的Stateless改为Stateful,通过Connectivity的流实时监听网络变化,自动控制弹窗的显示和关闭:
class MyApp extends StatefulWidget { final SharedPreferences prefs; final bool isLoggedIn; MyApp(this.prefs, this.isLoggedIn); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription; bool _isNoNetwork = false; @override void initState() { super.initState(); // 初始化检查网络状态 _checkInitialConnectivity(); // 监听网络状态变化 _connectivitySubscription = Connectivity().onConnectivityChanged.listen((results) { final hasNetwork = results.any((result) => result != ConnectivityResult.none); setState(() { _isNoNetwork = !hasNetwork; }); if (hasNetwork && _isNoNetwork) { // 网络恢复,关闭弹窗 Navigator.pop(context); } else if (!hasNetwork) { // 网络断开,显示弹窗 _showNoNetworkDialog(); } }); } Future<void> _checkInitialConnectivity() async { final results = await Connectivity().checkConnectivity(); setState(() { _isNoNetwork = !results.any((result) => result != ConnectivityResult.none); }); if (_isNoNetwork) { WidgetsBinding.instance.addPostFrameCallback((_) => _showNoNetworkDialog()); } } void _showNoNetworkDialog() { // 避免重复弹出相同弹窗 if (ModalRoute.of(context)?.isCurrent == false) return; showDialog( context: context, barrierDismissible: false, // 禁止点击外部关闭弹窗 builder: (context) =>
相关产品推荐
相关产品推荐

