Flutter remove dialogs in sequence - flutter

I am trying to remove dialogs in a specific sequence but I am getting an following error:
E/flutter (14457): [ERROR:flutter/shell/common/shell.cc(188)] Dart Error: Unhandled exception:
E/flutter (14457): Looking up a deactivated widget's ancestor is unsafe.
E/flutter (14457): 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 inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
Here the sequence:
List item
Open Confirm dialog 1
Close (onPressed) dialog 1
Open Loading dialog 2.
Close (Navigator.of(context).pop())
Open dialog 3 with success message.
Both Dialog 1 and 2 cloese with Navigator.of(context).pop().
How can I fix this?

When you use the function Navigator.of(context).pop(),don't use the context from previous dialog,try to use the page's context。
The reason about Looking up a deactivated widget's ancestor is unsafe is previous dialog is closed,but you use that dialog's context. You can see the source code:
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
}) {
final NavigatorState navigator = rootNavigator
? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())
: context.ancestorStateOfType(const TypeMatcher<NavigatorState>());// here check the ancestor state,and throw the error
assert(() {
if (navigator == null && !nullOk) {
throw FlutterError(
'Navigator operation requested with a context that does not include a Navigator.\n'
'The context used to push or pop routes from the Navigator must be that of a '
'widget that is a descendant of a Navigator widget.'
);
}
return true;
}());
return navigator;
}

Related

flutter/dart: Looking up a deactivated widget's ancestor is unsafe

I have used persistent_bottom_nav_bar 5.0.2 package and from the first tab page which is homepage I navigate to further some pages and than finally I use the following navigator to come back to this page on same tab
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
(_) => false,
);
I Navigate successfully but when I click on this tab it throw this exception
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
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.
I even don't know the reason as well as solution so any help will be appreciated.
You are removing all the screens below HomePage from the stack.
pushAndRemoveUntil as name suggests pushes a new Screen in stack and removes all the other screens from stack as a result theirs no screen available to pop except the current HomePage.
Instead use only
if (mounted) {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
(_) => false,
);
}

showModalBottomSheet and Unhandled Exception: setState() called after dispose() on parent widget

Context:
I have a modal bottom sheet that pops up, upon selection of Camera/Gallery acquires/selects an image XFile and returns it for processing (uploading) done with the help of image_picker.
This is done with a sample line:
ListTile(
onTap: () {
// definition: Future<XFile?> showCamera(IdPhotoOrientation orientation);
showCamera(orientation).then((value) => Navigator.of(context).pop<XFile?>(value));
},
...
),
Picking an image with showModalBottomSheet is done by returning the selected XFile and processing it on a chained function _handleFile(XFile, enum):
return showModalBottomSheet<XFile?>(
context: context,
builder: (context) {
return SingleChildScrollView(
child: ListBody(
children: [
...
ListTile(
onTap: () {
showCamera(orientation).then((value) => Navigator.of(context).pop<XFile?>(value));
},
leading: Icon(Icons.camera),
title: Text("From Camera"),
),
...
],
),
);
},
).then((value) => _handleFile(value, orientation));
What is the problem:
While processing file in _handle(XFile?, int), I need to update the state of the app to show progress bar updates, circular indicators, uploading status, etc.
Future<void> _handleFile(XFile? xfile, int orientation) {
if (xfile == null) {
return Future.value();
}
// store locally with Uploading Status
var imageService = locator<ImageService>();
setState(() { <-------- offending line (ui_partner_registration_id_photos.dart:103:5)
remoteImageStatus[xfile] = UploadStatus.Uploading;
images[orientation] = xfile;
});
// Upload and update result / error
return imageService.uploadIDPhoto(File(xfile.path), orientation).then((value) {
setState(() {
idPhotos[orientation] = value;
remoteImageStatus[xfile] = UploadStatus.Done;
});
print("Uploaded [${xfile.path}]");
}).onError((error, stackTrace) {
print("Error uploading image");
print(stackTrace);
setState(() {
remoteImageStatus[xfile] = UploadStatus.Error;
});
});
}
Why is this a problem?
setState() cannot be called on a stateful widget that is no longer visible/active/in-focus which is now the case for the showModalBottomSheet. That being said, after calling Navigator.pop() this should no longer be the case as the parent stateful widget is now in focus, this is causing my confusion.
(temporary) Solution
A temporary solution (which does not give exactly the desired result) is to add a mounted check as described here with an example here:
if (mounted) {
setState((){
// perform actions
})
}
StackTrace:
[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: setState() called after dispose(): _RegisterIDPhotosState#b75f9(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().
#0 State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1052:9)
#1 State.setState (package:flutter/src/widgets/framework.dart:1087:6)
#2 _RegisterIDPhotosState._handleFile (my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:103:5)
#3 _RegisterIDPhotosState.pickImageWithModalPopup.<anonymous closure> (package:my-awesome-app/viewcontrollers/register/partner/ui_partner_registration_id_photos.dart:188:23)
#4 _rootRunUnary (dart:async/zone.dart:1362:47)
#5 _CustomZone.runUnary (dart:async/zone.dart:1265:19)
<asynchronous suspension>
Question:
After selecting a file and starting the upload process, how can I call setState() as in the example of _handleFile(XFile?, int) above?
Refactor that logic to a ChangeNotifier or ValueNotifier higher up in the widget tree and make your Widgets use it to share state between them see the official docs for a more in thorough description.
The setState approach won't work because you are handling 2 different widgets there. You state:
"That being said, after calling Navigator.pop() this should no longer be the case as the parent stateful widget is now in focus, this is causing my confusion."
Whats causing your confusion is that setState is not a global callback which is executed in the currently focused Sateful Widget, setState is nothing more than executing your callback and calling markNeedsBuild for the specific widget in which the setState call was made, which in your case is no longer mounted.
That being said the docs I pointed you to is a recommended way of sharing state in a Flutter app.

Close `showModalBottomSheet` after a timeout

I tried to close showModalBottomSheet widget through an event listener.
showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (newcontext) {
return MyBottomDialog();
});
I tried to close this through an event listener:
class _MyBottomDialogState extends State<MyBottomDialog> {
...
...
void initState() {
_myEventStream.listen((state) {
if (state == 'timeout') {
Navigator.of(context, rootNavigator: true).pop();
}
});
super.initState();
}
TypeError: Cannot read property 'findAncestorStateOfType' of null
.
.
.
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.
How do I achieve this?
The stream class actually comes with a timeout method. You just have to feed it a duration and if it exceeds that duration it throws a TimeoutException. Wrap the execution in a try block and call the close modal sheet method from the on TimeoutException catch (e) block perhaps.
May be I didn't capture properly what the error is saying.
Even after closing the dialog the stream subscription persists. So the handler is called even if the dialog is not open.
Adding _streamSubscription.cancel() in the widget's dispose method helps solve this problem.

Flutter Navigation from Firestore Flag

Diagram of what I want to do]1
I want to navigate to Screen2 from screen1 when the value of screen2_flag changes to true (on firebase cloud firestore ) ,
I have screen 1 as shown in the diagram ,which is working perfectly fine until the value of screen2_flag is false , when I change the value of flag from false to true .
void initState() {
super.initState();
DocumentReference reference =
Firestore.instance.collection('myColection').document('myDoc');
reference.snapshots().listen((querySnapshot) {
print('got sanpshot' + querySnapshot.data['screen2_flag'].toString());
if (querySnapshot.data['screen2_flag'].toString() == 'true') {
Navigator.pushNamed(context, screen2.id);
}
});
Widget build(BuildContext context) {
// my code
...
}
}
I am able to see screen2 on my emulator. But Getting below error on Console
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() callback argument returned a Future.
E/flutter (24551): The setState() method on _WaitingRoomState#25dfa was called with a closure or method that returned a Future. Maybe it is marked as "async".
E/flutter (24551): Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().
E/flutter (24551): #0 State.setState. (package:flutter/src/widgets/framework.dart:1151:9)
E/flutter (24551): #1 State.setState (package:flutter/src/widgets/framework.dart:1167:6)
The error told you the problem:
setState() callback argument returned a Future
The setState() method on _WaitingRoomState#25dfa was called with a
closure or method that returned a Future
and told you also the solution:
Instead of performing asynchronous work inside a call to setState(),
first execute the work (without updating the widget state), and then
synchronously update the state inside a call to setState()
So you do the async call,await for it until you get the flag value(as I suppose because you didn't show that part of code), then call setState() with the new values updated.

Flutter app crash after converting Provider 3 to 4

I tried to upgrade my Flutter app to use Provider 4.0.1 today and the following code crashed on assigning a value to null.
Here is the code I am attempting to convert. I only changed SingleChildCloneableWidget to SingleChildStatelessWidget which compiled OK.
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
List<SingleChildStatelessWidget> providers = [
...independentServices,
...dependentServices,
...uiConsumableProviders
];
List<SingleChildStatelessWidget> independentServices = [
Provider.value(value: Api()),
Provider.value(value: Tbl()),
Provider.value(value: Bill()),
Provider.value(value: Sale()),
Provider.value(value: Category()),
Provider.value(value: Menu()),
];
List<SingleChildStatelessWidget> dependentServices = [
ProxyProvider<Api, AuthenticationService>(
update: (context, api, authenticationService) => AuthenticationService(api: api),
),
];
List<SingleChildStatelessWidget> uiConsumableProviders = [
StreamProvider<User>(
create: (context) => Provider.of<AuthenticationService>(context, listen: false).user,
),
lazy: false
];
I implemented it like this:
StreamController<User> _userController = StreamController<User>();
Stream<User> get user => _userController.stream;
The crash occurred at this line:
Future<void> _setFixedLanguageStrings(BuildContext context) async {
User _user = Provider.of<User>(context);
_user.homeString = await translate(context, 'Home');
The getter 'language' was called on null. Receiver: null
This was working fine with Provider 3.0.3 but obviously I need to do more.
My original code came from this tutorial.
edit: I fixed that problem by adding lazy: false in the stream provider create method but then another error later in this code.
Future<String> translate(BuildContext context, _term) async {
final String _languageCode = Provider.of<User>(context).language;
which produced this error:
Exception has occurred.
_AssertionError ('package:provider/src/provider.dart': Failed assertion: line 213 pos 7: 'context.owner.debugBuilding || listen ==
false || _debugIsInInheritedProviderUpdate': Tried to listen to a
value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed)
that called Provider.of without passing listen: false.
To fix, write: Provider.of(context, listen: false);
It is unsupported because may pointlessly rebuild the widget
associated to the event handler, when the widget tree doesn't care
about the value. )
I added listen: false to the line above which seems to have fixed that problem, however the next provider I attempted to use produced this error:
Tried to listen to a value exposed with provider, from outside of the
widget tree.
This is likely caused by an event handler (like a button's onPressed)
that called Provider.of without passing listen: false.
To fix, write: Provider.of(context, listen: false);
It is unsupported because may pointlessly rebuild the widget
associated to the event handler, when the widget tree doesn't care
about the value. 'package:provider/src/provider.dart': Failed
assertion: line 213 pos 7: 'context.owner.debugBuilding || listen ==
false || _debugIsInInheritedProviderUpdate'
Should I now go to every instance where I call a provider and add listen: false? I need somebody to explain what has changed and why as I am fairly new at Flutter and the docs are sparse for Provider. There are many times where I call Provider in my code and this last error did not return a code location.
Is listen: false now always required when it wasn't before or have I missed something else? I am starting to add listen: false to every call to instantiate a Provider variable and it appears to be working but is this the correct approach? Should I just add listen: false to every call to Provider.of and call it a day?
edit: The error arises whenever the provider is called from outside the visible part of the widget tree. This distinction is important.
I have the same "problem", if i add listen: false everywhere i call Provider the problem is gone but i dont know if thats the right solution...?
On Event Handlers like onPressed, OnTap, onLongPressed etc. we must use
Provider.of<T>(context,listen:false)
reason being that they will not listen for any update changes, instead are responsible for making changes.
whereas widgets like Text etc. are responsible for displaying...hence need to be updated on every change made....therefore use
Provider.of<T>(context,listen:true) //by default is listen:true
listen:true being the default is logical.
It's not specifying inside an event handler that is not logical.listen: false
Also, 4.1.0 will somehow have a shorter alternative to Provider.of:
context.read<T>() // Provider.of<T>(context, listen: false)
context.watch<T>() // Provider.of<T>(context)
listen : false called when the data wouldn't updating any thing in the UI, and should be used, like removing all cards in a widget when button clicked.
For more info's, read this go_to_link
In my case I was getting the following error:-
I/flutter ( 7206): Tried to listen to a value exposed with provider, from outside of the widget tree.
I/flutter ( 7206):
I/flutter ( 7206): This is likely caused by an event handler (like a button's onPressed) that called
I/flutter ( 7206): Provider.of without passing `listen: false`.
I/flutter ( 7206):
I/flutter ( 7206): To fix, write:
I/flutter ( 7206): Provider.of<AstroDetailsProvider>(context, listen: false);
I/flutter ( 7206):
I/flutter ( 7206): It is unsupported because may pointlessly rebuild the widget associated to the
I/flutter ( 7206): event handler, when the widget tree doesn't care about the value.
As you can see that solution is present in the error message itself.
Hence, we are not supposed to use provider with default (listen:true) inside event handlers.
Alternatively,
context.read<T>() is same as Provider.of<T>(context, listen: false) And
context.watch<T>() is same as Provider.of<T>(context)```
Ref :- https://github.com/rrousselGit/provider/issues/313
If you are using provider outside the build without listen: false
then, of course, you can't listen to changes as it didn't build the widgets again for changes. this doesn't come as a default value because the provider is not supposed to use outside the build as it used as a state management tool and to inject dependencies.
But however, if you are using outside the build you have to use listen: false
If we use,
Provider.of<T>(context, listen: true).method();
inside build method and if we have
notifyListeners();
in that method(), then it causes infinite recursion , as on each
notifyListeners();
invoke ,all provider calls with listen : true will execute , i.e build method is re executed, that means
method(),
is called again and again and result in error