Looking up a deactivated widget's ancestor is unsafe - flutter

I am new in Flutter and I am trying receive data with a Dialog.
When a click in textField the error of image2 appear...
show(BuildContext context){
var dialog = Dialog(
child: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)))),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
)
],
),
),
),
);
showDialog(context: context,builder: (context){
return dialog;
});
}
This is my code.
I/flutter (31032): Looking up a deactivated widget's ancestor is unsafe.
I/flutter (31032): At this point the state of the widget's element tree is no longer stable. To safely refer to a
I/flutter (31032): widget's ancestor in its dispose() method, save a reference to the ancestor by calling
I/flutter (31032): inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
I/flutter (31032):

Declare a global variable
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
then register the key on your widget build's scaffold eg
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
...
then on the dialog
show(BuildContext context){
var dialog = Dialog(
child: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)))),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
)
],
),
),
),
);
Pass that scaffold context to the showDialog method
showDialog(context: _scaffoldKey.currentContext ,builder: (context){
return dialog;
});
}

Try This
Give different context name for dialog
showDialog(context: context,builder: (dialogContex){
return Dialog(
child: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)))),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(dialogContex).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
)
],
),
),
),
);
});

I got the same error when attempting to open a dialog and I found a solution here: github flutter issues. Specifically, I followed the poster's recommendation, which was to create a GlobalKey and associate it with the Scaffold widget, and use the context from that key when creating the dialog. In my case, I have a globally accessible object which holds the GlobalKey:
MyGlobals myGlobals = MyGlobals();
class MyGlobals {
GlobalKey _scaffoldKey;
MyGlobals() {
_scaffoldKey = GlobalKey();
}
GlobalKey get scaffoldKey => _scaffoldKey;
}
In the Scaffold widget constructor call:
Scaffold(
appBar: ...,
body: ...,
drawer: ...,
key: myGlobals.scaffoldKey,
)
And in the showDialog call:
showDialog<String>(
barrierDismissible: ...,
builder: ...,
context: myGlobals.scaffoldKey.currentContext,
);

You’re trying to access a context that isn’t probably available. That happens because you’ve assigned your Dialog to a var and afterwards use a different context (the one from your dialog builder).
Either create your dialog directly after your return in the builder or make it a method instead that returns a Dialog and pass it a BuildContext parameter.
Widget myDialog(BuildContext context) => Dialog(/*your dialog here*/);
This is also a more convenient Flutter practice. You should use methods that return widgets instead of assigning it to variables.

use this:
Navigator.of(context,rootNavigator: true).pop();
instead of
Navigator.of(context).pop();

This might happen while you are popping from the context and trying to open new content on the context you are popping.
()async{
Navigator.of(context).pop();
_alertPopUp(); // shows a dialog
// might do some work after
}
if alert dialog is created on current context then it throws an error because context doesn't exist anymore

My problem was that I was using hot reload for pretty long time, I think at some point everything got messed up, doing a normal run of the app fixed the problem.

removing application from emulator and run below commands
flutter clean
flutter pub get
works for me

Though you got desired answer, just for better clarification for others I put my opinion here.
Reason :
It is happend due to context mismatch issue. Your passing context to Navigator.of(context).pop() is not matching with your MainApp BuildContext.
Solution : There has 2 way
U can use Global key
pass actual context to your Navigator
Below link I already mentioned how to solve this by passing actual context
https://stackoverflow.com/a/73543251/6109034

Try this:
Future<AlertDialog> myDialog(BuildContext context) {
return showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
margin: EdgeInsets.all(8.0),
child: Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: "Insira o número de telefone",
border: OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(2.0)))),
),
],
),
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Cancelar")),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Aceitar"))
],
);
},
);
}

declare dialog and set in initState
late Dialog dialog;
#override
void initState() {
super.initState();
dialog = Dialog(
...
);
}

before calling a dialog when a page is just loading, call it by adding SchedulerBinding to it, call it like this
SchedulerBinding.instance?.addPostFrameCallback((_) => showDialog( context: context, barrierDismissible: false, builder: (context) { return dialogBox(context, "Fetching account data", 'Profile page', DialogType.processing, function: () {}, dismissText: "", ); }));

In my case I was using a provider where I used a context as an argument to a function, the thing was that when I passed that page I did it with pushnamedAndRemove Until then on the next page I was trying to use a function where I required the above context, so the error was mine because it was trying to get a parameter that I destroyed earlier, for that reason it didn't work. So be careful if you are deleting old pages.

Use this if You are using Stack in AlertDialog Not Closing on Navigator.of(context).pop();
late NavigatorState _navigator;
#override
void didChangeDependencies() {
_navigator = Navigator.of(context);
super.didChangeDependencies();
}
Use This
Positioned(right: 10.0,child: GestureDetector(
// behavior: HitTestBehavior.translucent,
onTap: () {
_navigator.pop(context);
},
child: Align(
alignment: Alignment.topRight,
child: CircleAvatar(
radius: 14.0,
backgroundColor: Colors.white,
child: Icon(Icons.close, color: black),
),
),
),
),

I simply solved this by wrapping the showDialog with a Builder widget, though for me the error came from a stream builder I simply wrap the stream builder with a builder widget and the remove the notify listeners from the a stream am calling in the stream builder, but in your case wrap the showDialog with a Builder widget and it will use the context from the builder, problem solved

first : declare a FormKey.
GlobalKey<FormState>myFormKey=GlobalKey<FormState>();
second : add the FormKey to your Form widget.
Form(
key:myFormKey,
child:child
)

In my case i was calling
setState(() {
Navigator.pop(context);
});

Related

Bloc provider above OverlayEntry flutter

I am having some problems with my flutter app. I am trying to add an overlay like this in the photo below:
And it works just fine, I am able to open it on long press and close it on tap everywhere else on the screen.
The problem is that those two buttons - delete and edit - should call a bloc method that then do all the logic, but I do not have a bloc provider above the OverlayEntry. This is the error:
Error: Could not find the correct Provider<BrowseBloc> above this _OverlayEntryWidget Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that _OverlayEntryWidget is under your MultiProvider/Provider<BrowseBloc>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>().toString()),
);
}
```
consider using `builder` like so:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context, child) {
// No longer throws
return Text(context.watch<Example>().toString());
}
);
}
```
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
I've already encountered this error but this time I'm in a bit of trouble because I'm working with an overlay and not a widget.
This is my code:
late OverlayEntry _popupDialog;
class ExpenseCard extends StatelessWidget {
const ExpenseCard({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<AppBloc, AppState>(
listener: (context, state) {},
buildWhen: (previous, current) => previous.theme != current.theme,
builder: (context, state) {
return Column(
children: [
GestureDetector(
onLongPress: () {
_popupDialog = _createOverlay(expense);
Overlay.of(context)?.insert(_popupDialog);
},
child: Card(
...some widgets
),
),
const Divider(height: 0),
],
);
},
);
}
}
OverlayEntry _createOverlay(Expenses e) {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _popupDialog.remove(),
child: AnimatedDialog(
child: _createPopupContent(context, e),
),
),
);
}
Widget _createPopupContent(BuildContext context, Expenses e) {
return GestureDetector(
onTap: () {},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.9,
decoration: BoxDecoration(
color: LocalCache.getActiveTheme() == ThemeMode.dark ? darkColorScheme.surface : lightColorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...some other widgets
],
),
),
SizedBox(
width: 256,
child: Card(
child: Column(
children: [
InkWell(
onTap: () {
_popupDialog.remove();
// This is where the error is been thrown
context.read<BrowseBloc>().add(SetTransactionToEdit(e));
showBottomModalSheet(
context,
dateExpense: e.dateExpense,
total: e.total,
transactionToEdit: e,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
children: [Text(AppLocalizations.of(context).edit), const Spacer(), const Icon(Icons.edit)],
),
),
),
const Divider(height: 0),
InkWell(
onTap: () {
_popupDialog.remove();
// This is where the error is been thrown
context.read<BrowseBloc>().add(DeleteExpense(e.id!, e.isExpense));
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
children: [Text(AppLocalizations.of(context).delete), const Spacer(), const Icon(Unicons.delete)],
),
),
),
],
),
),
),
],
),
);
}
How can I add the bloc provider above my OverlayEntry? Is this the best course of action?
Thank you to everyone that can help!
Wrap your widget that you use in OverlayEntry in BlocProvider.value constructor and pass the needed bloc as an argument to it, like so
OverlayEntry _createOverlay(Expenses e, ExampleBloc exampleBloc) {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _popupDialog.remove(),
child: BlocProvider<ExampleBloc>.value(
value: exampleBloc,
child: AnimatedDialog(
child: _createPopupContent(context, e),
),
),
),
);
}
I have found a solution starting from the answer of Olga P, but changing one thing. I use the BlocProvider.value but I am passing as an argument to the method the context and not the bloc itself. This is the code:
OverlayEntry _createOverlay(Expenses e, BuildContext context) {
return OverlayEntry(
builder: (_) => GestureDetector(
onTap: () => _popupDialog.remove(),
child: BlocProvider<BrowseBloc>.value(
value: BlocProvider.of(context),
child: AnimatedDialog(
child: _createPopupContent(context, e),
),
),
),
);
}
With this change the two methods - edit and delete - work perfectly. Thanks to everyone who replied, I learned something today too!
The problem is that you are using a function and not a widget. So you can either modify _createOverlay to be stateless or stateful widget, or you can pass the bloc as an argument to the function.
In the latter case this would be _createOverlay(expense, context.read<AppBloc>())

Why I open this showDialog and after press one of the button, it can't auto close?

When I open this Dialog and after press one of the button, it can't auto close.
Is my navigator wrong? ? How to fix this problem ? Please help me, thanks.
I can’t understand why this pop-up window still stays on the page and my page jumped one page forward, shouldn’t it be popped by the Navigator?
this is my code first widget is the Delete function&UI, second widget is show main List widget, it use ListTile.
Widget _checkDelete({String title, String detail, String uid}) {
ThemeData themeData = Theme.of(context);
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: Container(
padding: EdgeInsets.all(16),
decoration: new BoxDecoration(
color: themeData.backgroundColor,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10.0,
offset: const Offset(0.0, 10.0),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$title",
style: AppTheme.getTextStyle(
themeData.textTheme.headline6,
fontWeight: FontWeight.w600,
)),
Text("$detail",
style: AppTheme.getTextStyle(
themeData.textTheme.subtitle1,
fontWeight: FontWeight.w400,
)),
Container(
alignment: AlignmentDirectional.centerEnd,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.all(0),
splashColor: themeData.colorScheme.primary.withAlpha(150),
onPressed: () {
Navigator.pop(context);
},
child: Text(
"取消",
style: AppTheme.getTextStyle(
themeData.textTheme.bodyText2,
fontWeight: FontWeight.w500,
color: themeData.colorScheme.primary),
)),
FlatButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.all(0),
splashColor: themeData.colorScheme.primary.withAlpha(150),
onPressed: () async {
final db = Firestore.instance;
db
.collection('users')
.document(uid)
.updateData({'staff': false}).then((value) =>
showDialog(
context: context,
builder: (BuildContext context) =>
_simpleDialog(
title: '刪除成功',
detail: '已經成功移除客服')));
Navigator.pop(context);
initList();
},
child: Text(
"刪除",
style: AppTheme.getTextStyle(
themeData.textTheme.bodyText2,
fontWeight: FontWeight.w500,
color: Colors.red),
)),
],
),
),
],
),
),
);
}
ThemeData themeData;
ListView _staffList = ListView(
padding: EdgeInsets.all(16),
children: <Widget>[],
);
Future<Null> _onRefresh() async {
await Future.delayed(Duration(milliseconds: 2000));
initList();
return null;
}
initList() async {
final db = Firestore.instance;
db
.collection('users')
.where('staff', isEqualTo: true)
.getDocuments()
.then((datas) {
setState(() {
_staffList = ListView(
padding: EdgeInsets.all(16),
children: <Widget>[
for (var data in datas.documents)
ListTile(
leading: CircleAvatar(
child: Text(data.data['name'][0]),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () async{
showDialog(
context: context,
builder: (BuildContext context) => _checkDelete(
title: '確定要移除 ${data.data['name']} 的權限嗎?',
detail: '這個帳號將恢復為一般帳號,不會被刪除。',
uid: data.documentID));
}), //this showDialog can't close when I pressed button
subtitle: Text(data.documentID),
title: Text(data.data['name']),
),
],
);
});
});
}
#override
initState() {
super.initState();
initList();
}
There can be multiple Navigator exist in your widget tree. The first one is the root Navigator, and under it there are other nested Navigators.
When you display a dialog, you need to call the showDialog function. This method has a property useRootNavigator that is default to true, which means the dialog route created by this method is pushed to the root Navigator. This is from the documentation:
The useRootNavigator argument is used to determine whether to push the dialog to the [Navigator] furthest from or nearest to the given context. By default, useRootNavigator is true and the dialog route created by this method is pushed to the root navigator. It can not be null`.
In order to pop the Dialog, you need to use the context of the root Navigator as well. Because you are using the context of the nested Navigator, the Dialog is not going away. That's why you need to include the rootNavigator: true in your method calling:
Navigator.of(context, rootNavigator: true).pop()
You can read more about the showDialog method from the documentation here.
do this
showDialog(...,builder:(theContextYouNeedToUse)=>_checkDelete(...,theContextYouNeedToUse));
and in the pop call
_checkDelete(...,BuildContext theContextYouNeedToUse){
...
Navigator.pop(theContextYouNeedToUse);
}
this is happening because, wrong context was used to pop, call to showDialog gives you a new BuildContext to use through the builder function, they have given the builder param for the sole purpose of providing you with the new BuildContext to use further down the tree
see this amazing answer https://stackoverflow.com/a/49100439/12341099

Enable/disable CupertinoDialogAction depending on CupertinoTextField is empty or not

I want to set CupertinoDialogAction to enable if CupertinoTextField is not empty else by default it should be disabled, also I have set the "isDefaultAction: false" but it is still clickable.
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
actions: [
CupertinoDialogAction(
onPressed: () => (Navigator.of(context).pop()),
child: Text("Cancel"),
),
CupertinoDialogAction(
child: Text("Save"),
isDefaultAction: false,
),
],
title: Text("New Folder"),
content: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Enter a name for this folder"),
),
Container(
height: 30,
child: CupertinoTextField(
controller: folderName,
placeholder: "Name",
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
)
],
),
),
);
If you want to disable a CupertinoDialogAction, you need to set the onPressed property to null. It will look like this:
Bool isEnabled = false;
#override
void initState() {
super.initState();
folderName.addListener(enableButton); // addListened to your TextEditingController!
}
It will set isEnabled to true.
enableButton()
{
if(folderName.text != "")
{
setState(() {
isEnabled = true;
});
}
}
And, then you can use this boolean field.
CupertinoDialogAction(
onPressed: !isEnabled
? null
: () {
// Do what you need!
// Save method!
},
child: Text("Save"),
isDefaultAction: false,
),
Create a stateful widget that builds the list of actions and returns CupertinoAlertDialog with those actions. This widget should contain some state that indicates if the save action should be enabled or not. If it should not be enabled, put null into the onPressed handler.
Write some handler that uses setState to set this enabled/disabled state depending on what the user is doing.
Return your stateful widget from the showDialog builder

Widgets tree rebuild using FocusScope.of(context).unfocus()

I have this example:
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(30),
child: GestureDetector(
onTap: () {
print('Hide keyboard!!!');
FocusScope.of(context).unfocus();
},
child: Column(
children: [
Text(DateTime.now().toIso8601String()),
TextFormField()
],
),
),
),
),
);
}
When the keyboard appears or is hidden it causes the widget to rebuild. Why does this happen?
Actually, I couldn't find the reason behind the rebuild after using
FocusScope.of(context).unfocus();
But This one will help you to stop rebuild the widget.
FocusManager.instance.primaryFocus.unfocus();
It's working on my application.

flutter streams bloc to show handle visible state

I have a widget isLive that changes state based on the value returned by the bloc.However everytime i run the app i get
The getter 'progressStateStream' was called on null
I tried following this answer
Widget isLive() {
return Container(
child: StreamBuilder<bool>(
stream: _bloc.progressStateStream,
builder: (context, snapshot) {
return Visibility(
maintainState: true,
visible: snapshot.data ?? false,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10)),
child: Container(
color: Colors.pink[50],
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text("yaay i'm visible"),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
child: Text(
"hide"
),
color: Colors.white,
onPressed: () {
_bloc.changeProgressState(state: false);
},
)
],
),
),
),
);
},
));
}
here is my bloc
//this Subject allows sending data, error and done events to the listener
final PublishSubject<bool> _progressStateSubject = new PublishSubject();
//the listener are streaming on changes
Observable<bool> get progressStateStream => _progressStateSubject.stream;
//to change your progress state
void changeProgressState({bool state}) => _progressStateSubject.sink.add(state);
Also if i wanted to save state with hydrated bloc how would I go about it
fixed it by initialising my bloc in init state by adding the bloc to init state
_bloc = RBloc();
_bloc.progressStateStream;