Flutter/Dart:setState()在用户重新打开应用程序时调用dispose()错误后调用



我制作了一个简单的应用程序,在其中我实现了Firebase身份验证,每当用户退出应用程序并重新打开应用程序时,StreamBuilder都会检查authState是否有数据。如果它让用户重定向到主页,但如果没有,则显示登录页面

问题是,当用户在关闭并重新打开应用程序后重定向到homeScreen时,在控制台中显示带有堆栈跟踪的dispose((错误后调用的setState((。

这是我的LoginScreen,当抛出异常时,它显示在堆栈跟踪中:

class LoginScreen extends StatefulWidget {
static const rout = '/login-screen';
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final Map<String, String> _loginData = {'email': '', 'password': ''};
GlobalKey topWidgetKey = GlobalKey();
GlobalKey bottomWidgetKey = GlobalKey();
double topWidgetHeight = 0.0;
double bottomWidgetHeight = 0.0;
double spacer = 0.0;
var isLoading = false;
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
final topWidgetKeyContextt = topWidgetKey.currentContext;
if (topWidgetKeyContextt != null) {
final box = topWidgetKeyContextt.findRenderObject() as RenderBox;
topWidgetHeight = box.size.height;
}
final bottomWidgetKeyContextt = bottomWidgetKey.currentContext;
if (bottomWidgetKeyContextt != null) {
final box = bottomWidgetKeyContextt.findRenderObject() as RenderBox;
bottomWidgetHeight = box.size.height;
}
spacer = MediaQuery.of(context).size.height -
AppBar().preferredSize.height -
topWidgetHeight -
bottomWidgetHeight -
MediaQuery.of(context).viewPadding.top -
MediaQuery.of(context).viewPadding.bottom;
});
});
super.initState();
}
final _emailController = TextEditingController();
final _form = GlobalKey<FormState>();
final _passwordController = TextEditingController();
var visible = true;
final emailVerificationSyntax = RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+.[a-zA-Z]+");
void _errorDialog(String message) {
showDialog(
context: context,
builder: ((ctx) => AlertDialog(
title: const Text("An Error Accourd"),
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: const Text("Okay"))
],
)),
);
}
void push() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => const MyApp(),
),
(route) => false);
}
Future<void> _saveData() async {
final isValid = _form.currentState!.validate();
if (!isValid) {
return;
}
_form.currentState!.save();
setState(() {
isLoading = true;
});
try {
await Login().signInUser(_loginData['email'].toString(),
_loginData['password'].toString(), context);
push();
} catch (e) {
var errorMessage = 'Authentication Failed';
if (e.toString().contains('INVALID_EMAIL')) {
errorMessage = 'The email adress is not valid';
} else if (e.toString().contains('EMAIL_NOT_FOUND')) {
errorMessage = 'No User found with this Email';
} else if (e.toString().contains('INVALID_PASSWORD')) {
errorMessage = 'Invalid Password';
}
_errorDialog(errorMessage);
}
setState(() {
isLoading = false;
});
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromRGBO(255, 243, 18, 3),
body: SingleChildScrollView(
child: Form(
key: _form,
child: Column(children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 90),
child: Image.asset('assets/images/Logo.png'),
),
const SizedBox(
height: 10,
),
const SafeArea(
child: Padding(
padding: EdgeInsets.only(left: 3, right: 250, top: 10),
child: Text(
"Login",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 40),
//  textAlign: TextAlign.center,
),
),
),
const Padding(
padding: EdgeInsets.only(left: 20, right: 200, top: 3),
child: Text(
"Please Login to continue",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.normal,
fontSize: 15),
//  textAlign: TextAlign.center,
),
),
const SizedBox(
height: 15,
),
Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(20),
),
),
shadowColor: Colors.grey,
elevation: 10,
child: TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email-Address',
prefixIcon: Icon(
(Icons.person),
),
border: OutlineInputBorder(borderSide: BorderSide.none),
),
onSaved: (newValue) {
_loginData['email'] = newValue!;
},
validator: (value) {
if (!emailVerificationSyntax.hasMatch(value as String)) {
return "Incorrect Email-Adress Syntax";
}
if (value.isEmpty) {
return 'Please Enter Your Email Adress';
}
return null;
},
),
),
const SizedBox(
height: 15,
),
Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(20),
),
),
shadowColor: Colors.grey,
elevation: 10,
child: TextFormField(
controller: _passwordController,
obscureText: visible,
keyboardType: TextInputType.streetAddress,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(
(Icons.password),
),
suffixIcon: IconButton(
onPressed: () {
setState(() {
visible = !visible;
});
},
icon: const Icon(Icons.visibility)),
border: const OutlineInputBorder(borderSide: BorderSide.none),
),
validator: (value) {
if (value!.isEmpty) {
return "Please Enter Your Passowrd";
}
return null;
},
onSaved: (newValue) {
_loginData['password'] = newValue!;
},
),
),
const SizedBox(
height: 15,
),
isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () => _saveData(),
style: ElevatedButton.styleFrom(
//  backgroundColor: Color.fromARGB(255, 246, 214, 4)),
backgroundColor: Colors.white),
child: const Text(
'Login',
style: TextStyle(
color: Colors.black,
fontSize: 30,
fontWeight: FontWeight.normal,
),
),
),
const SizedBox(
height: 100,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(bottom: 20),
child: Text(
"Don't have an account?",
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: TextButton(
onPressed: () {
Navigator.of(context)
.pushReplacementNamed(SignUpScreen.signUpRout);
},
child: const Text(
"Sign Up",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold),
)),
)
],
)
]),
),
),
);
}
}

以下是控制台中显示的错误:

The following assertion was thrown during a scheduler callback:
setState() called after dispose(): _LoginScreenState#0f444(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
When the exception was thrown, this was the stack
#0      State.setState.<anonymous closure>
package:flutter/…/widgets/framework.dart:1078
#1      State.setState
package:flutter/…/widgets/framework.dart:1113
#2      _LoginScreenState.initState.<anonymous closure>
package:masaba_tul_eelaaf/…/LoginPage/login_screen.dart:25
#3      SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1175
#4      SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1113
#5      SchedulerBinding.scheduleWarmUpFrame.<anonymous closure>
package:flutter/…/scheduler/binding.dart:881
(elided 4 frames from class _RawReceivePortImpl, class _Timer, and dart:async-patch)

另一个解决方案是检查;安装";属性,然后调用setState((以确保该对象仍在树中。

如错误消息所示:

if(mounted) {
setState((){
...
});
}

编辑:解释:

setState将小部件标记为";需要重建";,从而使其能够在下一个可能的场合重新执行构建方法。然而,如果小部件本身不再在小部件树中,例如,因为您关闭并重新打开了应用程序,或者您有一个对话框被取消并且不再可见,那么在该小部件上调用setState会导致错误,因为它试图重建无法构建的东西(因为实例没有"安装"在小部件树中(。这种情况通常发生在调用小部件的dispose方法后,未来或某种回调调用setState时。

相关内容

最新更新