Flutter Navigator Page Change within async button press - flutter

I'm experimenting with flutter and now I reached a point, where I don't understand, why I get an exception. I already tried a few things.
What am I doing?
I'm pressing a button - this button executes an async function within unpressed handler. Inside, depending on the response, I want to change the page.
TextButton(
child: Text(
_title,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
onPressed: () async {
var response = await authBloc.onLogin(_akey, _password);
if (response.isValid) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Container(),
));
}
},
),
For me it looks fine. I even tried to do this within a scheduled binder (SchedulerBinding.instance.addPostFrameCallback((_)), but I'm getting the same exception.
Exception:
Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
Anyone can help?
Thanks in advance!

Related

How to dispose Timer Button in flutter?

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.

Flutter context error after multiple pops on dialogs

I have a function called from a button in one of my menu pages that builds an AlertDialog passing a context.
The dialog contains a button that calls a function (called testFunction) that:
first disposes the current dialog using the passed context;
then creates a new loading dialog;
then calls an async function which, when done, disposes the current loading dialog and creates a new final dialog.
But it gives me this error when I try to build the loading dialog on the third step:
E/flutter ( 2550): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 2550): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 2550): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
The function called from the menu button:
static void buildDeckPurchaseDialog(BuildContext context) {
showDialog(context: context, builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
width: 80,
height: 130,
child: Center(
MenuAnimatedButton(
width: 110,
height: 50,
function: () => testFunction(context), // Executed on button tap
),
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: kBoxColor,
);
});
}
testFunction() called from the button in the dialog built from the previous function:
Future<dynamic> testFunction(BuildContext context) async {
try {
// Disposing the previous dialog
Navigator.of(context).pop();
// Showing loading dialog
CustomDialogs.buildLoadingDialog(context, "Processing purchase...");
// Making the async request
return await FirebaseFunctions.instance.httpsCallable('test').call({
'test': 1,
}).then((value) {
// Disposing the loading dialog
Navigator.of(context).pop(); // <- ERROR HERE
// Building the last dialog (which is not shown)
CustomDialogs.buildSimpleDialog("End of function", context);
}).onError((error, stackTrace) => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const ErrorScreen())));
} on FirebaseFunctionsException {
Navigator.push(context, MaterialPageRoute(builder: (context) => const ErrorScreen()));
}
}
I think I should use didChangeDependencies() method but I don't know how.
What I was doing wrong was giving the context passed to the buildDeckPurchaseDialog function the same name as the context created by the showDialog function builder (builder: (BuildContext context)).
This way, testFunction(context) took the builder context as an argument and not the passed context.
Then write the function like this:
static void buildDeckPurchaseDialog(BuildContext passedContext) {
showDialog(context: passedContext, builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
width: 80,
height: 130,
child: Center(
MenuAnimatedButton(
width: 110,
height: 50,
function: () => testFunction(passedContext), // Executed on button tap
),
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: kBoxColor,
);
});
}

setState() or markNeedsBuild() called during build from changeNotifier

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

Flutter dialog with MVC

I am trying to display a dialog box with MVC pattern.
I want the dialog box to be a widget. like so:
AlertDialog gameDecisionDialog({
required VoidCallback onClick,
required String strDecision,
required Color decisionColor,
required BuildContext context,
}) {
return AlertDialog(
titleTextStyle: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 20,
),
actionsOverflowButtonSpacing: 20,
actions: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
return onClick();
},
child: const Icon(Icons.next_plan_outlined),
),
],
content: Text(strDecision),
);
}
This dialog will be called in the model layer. Depending what happens during the app, a dialog will appear. The issue, is the context portion.
does it make sense to pass context from the view layer down to controller layer and then to model layer? Seems inefficient.
Any ideas on how to do this? I am trying to avoid having the dialog box in the view layer, its going to get too messy.
---------------- UPDATE
modified my code to the below suggestion, BUT now my alert dialog doesn't show up.
See the following code (when button clicked do some stuff and then display dialog):
elevatedRectButton(
onClick: () {
setState(() {
MyController.stop();
gameDecisionDialog(
onClick: () {
MyController.start();
},
gameDecision: MyController.getGameDecision,
decisionColor: Colors.amber,
context: context,
);
});
},
mIcon: const Icon(Icons.do_not_touch),
subText: 'STOP',
minWidth: 20,
height: 20,
bgColor: Colors.red,
),
I fear that calling a widget within a widget might be causing this issue?
Passing a BuildContext into a model/controller would not be recommended. Try to call the alert from the widget after the work in the model has been done or when an error is thrown.
Example:
onPress: () async {
//Some trigger callback
startLoading();
await controller.doWork().catchError((e) {
stopLoading();
showAlert(context, 'Some Message etc');
});
stopLoading();
}

Two stateFullWidget error This widget has been unmounted, so the State no longer has a context

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.