I have a timer button that waits for 30 seconds then be active, it is used to allow the user resend OTP verification code. there is also a "Verify" button that verifies the entered OTP and completes the authentication and then resets some variabe through setState.
problem is when I press the Verify button while the timer button is still counting, an error happens saying:
Unhandled Exception: setState() called after dispose():
_TimerButtonState#70c61(lifecycle state: defunct, not mounted)
E/flutter ( 3950): 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.
E/flutter ( 3950): 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.
by testing I can see it only happens if setState happens while the counter is still running. but can't find a way to make it stop counting prior to setstate.
Any Idea how can I solve this?
Timer Button
TimerButton(
buttonType: ButtonType.TextButton,
label: "Resend Code".tr,
timeOutInSeconds: 30,
onPressed: () async {
await sendOTP();
},
resetTimerOnPressed: false,
disabledColor: Colors.grey,
color: const Color.fromARGB(255, 245, 91, 165),
disabledTextStyle:
TextStyle(fontSize: 12.sp, color: Colors.white),
activeTextStyle: TextStyle(
fontSize: 13.sp,
color: Colors.white,
),
),
Verify Button:
SizedBox(
height: 35.h,
width: 220.w,
child: ElevatedButton(
onPressed: () async {
if (loading == true) {
} else {
final PhoneAuthCredential phoneAuthCredential =
PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: otpcontroller.text);
signInWithPhoneAuthCredential(phoneAuthCredential);
}
},
child: Container(
width: 220.w,
alignment: Alignment.center,
child: Text(
"Verify".tr,
style: GoogleFonts.lato(
fontStyle: FontStyle.normal,
color: Colors.white,
fontSize: 20.sp,
),
),
),
style: TextButton.styleFrom(
primary: Colors.white,
backgroundColor: const Color.fromARGB(255, 245, 91, 165)),
),
Verification Function:
void signInWithPhoneAuthCredential(
PhoneAuthCredential phoneAuthCredential) async {
setState(() {
loading = true;
});
try {
// ignore: non_constant_identifier_names
final AuthCredential =
await _auth.signInWithCredential(phoneAuthCredential);
setState(() {
loading = false;
});
if (AuthCredential.user != null) {}
} on FirebaseAuthException catch (e) {
setState(() {
loading = false;
});
Flushbar(
message: e.message,
duration: const Duration(seconds: 4),
).show(context);
}
}
Didn't find good solution to this, instead I replaced the timer button with a normal TextButton and circular_countdown_timer plugin. and contollred the button become active/inactive using a bool that changes its value with when the timer finishes.
Related
I'm getting this error, and i know it's because of a line of code where I listen for data from Provider class, so, first question is, does changeNotifier call setState() when it needs to notifyListeners, or is it Provider calling setState or markNeedsBuild, I'm confused, also, please how to solve it, here is my code
here is where I'm using it
BottomNavigationBarItem(
icon: Badge(
showBadge:
Provider.of<NotificationsModel>(context, listen: false).unSeen > 0, // this is the problem
badgeContent: Text(
Provider.of<NotificationsModel>(context, listen: false).unSeen.toString(), // and this
style: TextStyle(
color: Colors.white,
fontFamily: kFontFamily,
fontWeight: FontWeight.w600,
fontSize: Dimensions.font7,
),
),
child: Icon(dModel.index == 2
? Icons.notifications
: Icons.notifications_paused_outlined),
),
label: 'Notifications'),
and here is unseen in changeNotifier class
int get unSeen {
int notSeen = 0;
for(Notification notification in _notifications) {
if(notification.isSeen == false) {
notSeen++;
notifyListeners();
}
}
return notSeen;
}
so, please, how can i make it stop trying to build or whatever it's doing, Thanks
Wrap your notifyListeners(); inside a Future.delayed(Duration.zero, notifyListeners);
this happens when you notifyListeners(); during the build screen process which will fire this exception because you are trying to update your UI while the initial UI is not built yet
In my flutter application I have a button for follow and unfollow a user.
buttonstate.isSelected && widget.user!.isFollowing == true
? ElevatedButton(
onPressed: () async {
await unfollowuser(widget.user!.id); //Api Call
},
child: Text(
'Following',
style: TextStyle(
color: Theme.of(context).disabledColor,
),
),
)
: ElevevatedButton(
onPressed: () async {
buttonstate.setButtonState(true);
await followuser(widget.user!.id); //Api Call
},
child: Text(
'Follow',
style: TextStyle(
color: Theme.of(context).accentColor,
),
),
),
My objective is whenever the button is pressed I want the Api call to happen as well as the state of the button should change from 'Follow' to 'Following'. Now the Api gets called, but the button wont change to 'following'. After the page is refreshed, button changes to 'following'. How can i manage state of the button using provider??
Ive created a provider for this purpose as follows
import 'package:flutter/material.dart';
class ButtonProvider with ChangeNotifier {
bool isSelected = false;
void setButtonState(bool value) {
isSelected = value;
notifyListeners();
}
bool? get buttondata {
return isSelected;
}
}
this question similar on flutter error: This widget has been unmounted, so the State no longer has a context (and should be considered defunct) flutter
i have two stateFullWidget
PersonScreen
_HandScreen
where in PersonScreen i have Column
...
PersonScreen
children [
Text('Hand'),
_HandScreen()
]
and then in HandScreen
i have DragTarget
DragTarget<String>(
onAccept: (value) async {
print('value $value');
await onAccept(value);
},
...
onAccept(value) async {
// i tried to open view alert or automatically navigator into any screen
try {
Alert(
context: context,
title: 'Finished!',
desc: 'You\'ve reached ',
image:Lottie.asset(
'assets/lotties/completed-check.json',
height: 85.0,
width: 85.0,
),
buttons: [
DialogButton(
child: Text(
"Selesai",
style: TextStyle(color: Colors.white, fontSize: 20),
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(),
color: Color.fromRGBO(0, 179, 134, 1.0),
),
],
).show();
} catch(e) {
print('error = $e'); // this throw This widget has been unmounted, so the State no longer has a context (and should be considered defunct)
}
}
but when i tried drag an item against (x2) into accept event, the alert can view or open.
i have change the instead of Alert with Navigator.pushNamed(context, MainScreen.id); the error is same.
i have tried with one stateFullWidget its look normal on running , but how i can handle it with two stateFullWidget , its posible ? because a lot of code in _HandScreen, i dont want to wrap into PersonScreen. by the way i use Provider instead of setState.
I am fetching data from firebase database in a async function which triggers when the button is pressed. When the data fetching is complete it moves to the next page(next acitivty). So, in this whole process the async on pressed function takes time to execute the whole code. So, i wanted to show a progress bar until the whole code is executed. Can anyone suggest me how can i show a progress bar until the new activity starts.
Here is my code:
FlatButton(
child: Text("Next",style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontFamily: "Calibri",
fontSize: 20)),
color: Colors.blue,
onPressed:() {
elist.then((value)
async {
for (var item in value) {
alleyes.add(new facial(criminal_id: item.criminal_id,
part_size: item.part_size,
link: item.link));
Uint8List bytes = (await NetworkAssetBundle(Uri.parse(item.link)).load(item.link)).buffer.asUint8List();
eye_str.add(String.fromCharCodes(bytes));
}
Navigator.push(context,
MaterialPageRoute(builder: (context)=>App()));
}
);
},
)
You can start by creating a variable called loading.
bool loading = false;
After this, you can set the loading to true when the button gets tapped and display a progress indicator when the loading variable is true
loading
? Center(child: CircularProgressIndicator())
: FlatButton(
child: Text("Next",style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontFamily: "Calibri",
fontSize: 20)),
color: Colors.blue,
onPressed:() {
setState((){
loading = true;
}); // set loading to true here
elist.then((value)
async {
for (var item in value) {
alleyes.add(new facial(criminal_id: item.criminal_id,
part_size: item.part_size,
link: item.link));
Uint8List bytes = (await NetworkAssetBundle(Uri.parse(item.link)).load(item.link)).buffer.asUint8List();
eye_str.add(String.fromCharCodes(bytes));
}
setState((){
loading = false;
}); // set it to false after your operation is done
Navigator.push(context,
MaterialPageRoute(builder: (context)=>App()));
}
);
},
)
I am using progress_dialog 1.2.0 package to show a progress dialog in my app, it is showing when I call pr.show() but not getting hidden when I call pr.hide():
onTap: () async {
pr.show();
print('clicked custom category');
print(categorylist[index].catName);
print(categorylist[index].catId);
// await getAllProductsInCategory(categorylist[index].catId);
setState(() {
catId = categorylist[index].catId;
myinitlist.clear();
myinitlist = List.from(productList);
pr.hide();
});
},
When I uncomment that getAllProductsInCategory() function it hides the dialog.
Try with :
onTap: () async {
pr.show();
print('clicked custom category');
print(categorylist[index].catName);
print(categorylist[index].catId);
setState(() {
catId = categorylist[index].catId;
myinitlist.clear();
myinitlist = List.from(productList);
Future.delayed(Duration(seconds: 3)).then((value) {
pr.hide().whenComplete(() {
print(pr.isShowing());
});
});
});
},
or :
onTap: () async {
pr.show();
print('clicked custom category');
print(categorylist[index].catName);
print(categorylist[index].catId);
Future.delayed(Duration(seconds: 3)).then((value) {
setState(() {
catId = categorylist[index].catId;
myinitlist.clear();
myinitlist = List.from(productList);
pr.hide().whenComplete(() {
print(pr.isShowing());
});
});
});
},
Please use await keyword when you are using async calls to start progressDialog & hide:
await progressDialog.show();
await progressDialog.hide();
Example:
Add the Package:
dependencies:
progress_dialog: ^1.2.4
import 'package:progress_dialog/progress_dialog.dart';
Create and initialize a ProgressDialog object inside the build() method passing context to it.
Initialize the ProgressDialog object:
final ProgressDialog pr = ProgressDialog(context);
By default it is a normal dialog to show some message, if you would like to use it to show percentage of progress done, specify the optional type parameter and specify if you want your dialog to dismiss when back button is pressed isDismissible parameter (Optional):
//For normal dialog
pr = ProgressDialog(context,type: ProgressDialogType.Normal, isDismissible: true/false, showLogs: true/false);
//For showing progress percentage
pr = ProgressDialog(context,type: ProgressDialogType.Download, isDismissible: true/false, showLogs: true/false);
> Note: Please initialize the ```ProgressDialog```, where you have availability of the context
Style the progress dialog (Optional)
pr.style(
message: 'Downloading file...',
borderRadius: 10.0,
backgroundColor: Colors.white,
progressWidget: CircularProgressIndicator(),
elevation: 10.0,
insetAnimCurve: Curves.easeInOut,
progress: 0.0,
textDirection: TextDirection.rtl,
maxProgress: 100.0,
progressTextStyle: TextStyle(
color: Colors.black, fontSize: 13.0, fontWeight: FontWeight.w400),
messageTextStyle: TextStyle(
color: Colors.black, fontSize: 19.0, fontWeight: FontWeight.w600)
);
Note: You don't need to use all parameters, all of them are optional.
Showing the progress dialog:
await pr.show();
Dynamically update the content shown out there
pr.update(
progress: 50.0,
message: "Please wait...",
progressWidget: Container(
padding: EdgeInsets.all(8.0), child: CircularProgressIndicator()),
maxProgress: 100.0,
progressTextStyle: TextStyle(
color: Colors.black, fontSize: 13.0, fontWeight: FontWeight.w400),
messageTextStyle: TextStyle(
color: Colors.black, fontSize: 19.0, fontWeight: FontWeight.w600),
);
Dismissing the progress dialog:
pr.hide().then((isHidden) {
print(isHidden);
});
// or
await pr.hide();
Navigating to next screens must be done after the completion of Future - hide(). See here for example.
Check if progress dialog is showing:
bool isProgressDialogShowing = pr.isShowing();
print(isProgressDialogShowing);
Use custom body
pr = ProgressDialog(
context,
type: ProgressDialogType.Normal,
isDismissible: true,
/// your body here
customBody: LinearProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blueAccent),
backgroundColor: Colors.white,
),
);
For more details: https://flutterrepos.com/repo/fayaz07-progress_dialog-