I can't figure out why 2 setState in async onPressed method causing error
Unhandled Exception: setState() called after dispose():
_LoginScreenState#0eb2c(lifecycle state: defunct, not mounted)
Here is my code
RaisedButton(
child: Text('Login'),
onPressed: emailController.text == "" || passwordController.text == ""
? null
: () async {
setState(() {
_isLoading = true;
});
SharedPreferences prefs =
await SharedPreferences.getInstance();
prefs.setString('email', emailController.text);
try {
await Provider.of<Auth>(context, listen: false)
.signIn(emailController.text, passwordController.text);
} on HttpException catch (error) {
exceptionAlertDialog(context, error.toString());
} catch (error) {
exceptionAlertDialog(context, error.toString());
}
setState(() {
_isLoading = false;
});
},
)
once isLoading is true I display spinner.. Nothing extraordinary
body: Container(
alignment: Alignment.center,
child: _isLoading
? Center(child: CircularProgressIndicator())
: ListView(
children: <Widget>[textSection(), buttonSection()],
),
),
Everything works fine if I comment out those 2 set state in the button onPressed.
try to put isLoading to dispose
#override
void dispose() {
_isLoading = false;
super.dispose();
}
Related
I'm trying to login in users using a third party api. But the problem is whenever an error occurs and the catch error is executed the "then" function that holds the navigation to the HomeScreen also is executed. Please is there a way to login user only when there is no error.
void signIn() {
setState(() {
_isLoading = true;
});
final isValid = _formKey.currentState!.validate();
if (isValid == false) {
setState(() {
_isLoading = false;
});
return;
} else {
setState(() {
_isLoading = true;
});
Provider.of<Authorization>(context, listen: false)
.loginUser(
_emailController.text.trim(),
_passwordController.text.trim(),
)
.catchError((error) {
const snackBar = SnackBar(
backgroundColor: Colors.red,
content: Text("An error occured please try again."),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}).then((_) {
setState(() {
_isLoading = false;
});
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) => const HomeScreen()),
),
);
});
}
}
Thanks in advance.
You can use another way to run this function and make it easier to you to understand and also more easy to code
void signIn() async{
setState(() {
_isLoading = true;
});
final isValid = _formKey.currentState!.validate();
if (isValid == false) {
setState(() {
_isLoading = false;
});
return;
} else {
setState(() {
_isLoading = true;
});
// here you need to make the login user function back a bool value either true or false true for success false for failed
final result = await Provider.of<Authorization>(context, listen: false)
.loginUser(
_emailController.text.trim(),
_passwordController.text.trim(),
)
// hide the loading
setState(() {
_isLoading = false;
});
// check if the result back from the function is true we success
if(result == true){
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) => const HomeScreen()),
),
);
}
// else we failed
else{
const snackBar = SnackBar(
backgroundColor: Colors.red,
content: Text("An error occured please try again."),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
}
this answer i think will work perfectly with you you just need to make the login user function return a Future and if success return true or if it failed return false and every thing will work successfully
thanks and i hope this answer helps you
Created a demo for understanding future..
I have taken two screen....Using Future.delayed I am forwarding to next screen in 5 seconds with CircularProgressIndicator...but when I move to back..it still showing CircularProgressbar...
What should I correct my code?
here is my code
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
bool isloading = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Async demo'),
),
body: isloading == true
? Center(child: CircularProgressIndicator())
: Center(
child: TextButton(
child: Text('Pressed'),
onPressed: () {
setState(() {
isloading = true;
});
Future.delayed(Duration(seconds: 5), () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return NextScreen();
}));
isloading = false;
});
},
),
),
);
}
}
What should I correct my code?
Wrap the second assignment of isloading to false inside setState (just as you did when setting to true). Something like the following:
onPressed: () {
setState(() {
isloading = true;
});
Future.delayed(Duration(seconds: 5), () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return NextScreen();
}));
setState(() {
isloading = false;
});
});
},
you need to use setState when you are assign variable = false
For that do as below
replace this
isloading = false;
with
setState(() {
isloading = false;
});
I'm trying to display a loading while doing an API Request and when finished to show the list with the response or a custom widget to show a message(EmptyListWidget). The problem is that the whenComplete() method is being executed before the async function is finished.
I also tried using then() and using FutureBuilder but I also can't make it work using Provider (allways returns null).
If someone could help, I would really appreciate it.. thanks :)
My List Widget:
class _AbsencesListState extends State<AbsencesList> {
bool _isLoading = false;
bool _isInit = true;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_isInit) {
setState(() => _isLoading = true);
Provider.of<AbsencesTypes>(context, listen: false)
.getAbsencesTypes(widget.ctx)
.whenComplete(() {
setState(() => _isLoading = false);
});
_isInit = false;
}
}
#override
Widget build(BuildContext context) {
final absences = Provider.of<Absences>(context).items;
return Stack(
children: [
_isLoading
? const Center(child: CircularProgressIndicator())
: absences.length > 0
? Container()
: EmptyListWidget(ListType.InconsistenciesList),
ListView.builder(
itemBuilder: (_, index) {
return GestureDetector(
onTap: () {},
child: Card(
elevation: 2.0,
child: ListTile(
leading: CircleAvatar(
child: const Icon(Icons.sick),
backgroundColor: Theme.of(context).accentColor,
foregroundColor: Colors.white,
),
title: Padding(
padding: const EdgeInsets.only(top: 3),
child: Text(absences[index].absenceType.name),
),
subtitle: Text(
absences[index].firstDate
),
),
),
);
},
itemCount: absences.length,
)
],
);
}
}
The async function:
class AbsencesTypes with ChangeNotifier {
List<AbsenceType> _absencesTypesList = [];
List<AbsenceType> get items {
return [..._absencesTypesList];
}
void emptyAbsencesTypeList() {
_absencesTypesList.clear();
}
Future<void> getAbsencesTypes(BuildContext context) async {
SharedPreferences _prefs = await SharedPreferences.getInstance();
String token = _prefs.getString(TOKEN_KEY);
http.get(
API_URL,
headers: {"Authorization": token},
).then(
(http.Response response) async {
if (response.statusCode == 200) {
final apiResponse = json.decode(utf8.decode(response.bodyBytes));
final extractedData = apiResponse['content'];
final List<AbsenceType> loadedAbsencesTypes = [];
for (var absenceType in extractedData) {
loadedAbsencesTypes.add(
AbsenceType(
id: absenceType["id"],
name: absenceType["name"].toString(),
code: absenceType["code"].toString(),
totalAllowedDays: absenceType["totalAllowedDays"],
),
);
}
_absencesTypesList = loadedAbsencesTypes;
} else if (response.statusCode == 401) {
Utility.showToast(
AppLocalizations.of(context).translate("expired_session_string"));
Utility.sendUserToLogin(_prefs, context);
}
notifyListeners();
},
);
}
}
Your problem here is probably that you're calling http.get without awaiting for it's result.
The getAbsencesTypes returns the Future<void> as soon as the http.get method is executed, without waiting for the answer, and it results in your onComplete method to be triggered.
A simple fix would be to add the await keyword before the http.get, but you could do even better.
In your code, you're not fully using the ChangeNotifierProvider which could solve your problem. You should check the Consumer class which will be pretty useful for you here, but since it's not your initial question I won't go more in depth on this subject.
I try to use https://pub.dev/packages/loading_overlay to display a simple loading overlay while making an async call in form.
class AccountFormState extends State<AccountForm> {
bool _loading = false;
override
Widget build(BuildContext context) {
return LoadingOverlay(
child: Form(
...
),
isLoading: _loading,
);
where in the form there is a
ElevatedButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
bool loginResult = await webService.testLoginSite(); // an async call
print('loginResult = $loginResult');
if (loginResult == true) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('successfull')));
}
}
},
child: Text('Check login'),
),
The problem is that the LoadingOverlay does not show overlay loading indicatow while making an async call in the ElevatedButton.
Why does it not work?
You need to setState _isLoading before and after webService.testLoginSite()
ElevatedButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() {
_isLoading = true;
});
bool loginResult = await webService.testLoginSite(); // an async call
print('loginResult = $loginResult');
setState(() {
_isLoading = false;
});
if (loginResult == true) {
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('successfull')));
}
}
},
child: Text('Check login'),
),
I want to return a bool in this method but it return a Future
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 0, right: 0, top: 110, bottom: 5),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
QuestionCards(),
cla().then((value) => { //this can't be add like this
YoutubeViewer(
videoId: 'hVQUbKs6qN8', topic: 'topic1'),
}
)
],
).
),
],
),
);
}
Future<bool> cla() async {
bool d = false;
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
return Future<bool>.value(true);
}
} on SocketException catch (_) {
print('not connected');
return Future<bool>.value(false);
}
}
If someone can tell me that what need to be changed in this
It would be really helpful
Thank you
Return bool using Future object
return Future<bool>.value(true);
and modify method like
Future<bool> cla() async{
Use like:
cla().then((value) {
// value will provide you true or false
});
Future<bool> cla() async {
bool d=false;
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
d= true;
}
} on SocketException catch (_) {
print('not connected');
d= false;
}
return d;
}
#override
void initState() {
super.initState();
// calling cla function
setState(() async {
var value = await getWeather();
if(value){
...
}
});
}
The observation you found is expected. Since the operation inside cla() function is async and the method is marked as async to return the future result. So you. will get future and to get the result form future you have to call await on it as shown above.
Future<bool> cla() async{
// function body goes here..
}
you can explicitly define return type of the method.