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

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,
);
}

Related

Flutter: go_router 6.0.1 KEYCODE_BACK throw null safety error in ShellRoute

In my application I have ShellRoute, which I use for BottomNavigation. If I use the system back button or gesture on the selected tab, I get a null safety error and I don't know how to get rid of it.
The error occurs in delegate.dart in class GoRouterDelegate. ↓
WillPopScope didn't work for me because the error was raised earlier.
The solution to my question was to redo the bottom navigation as shown here in example:
https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/shell_route.dart
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (BuildContext context, GoRouterState state, Widget child) {
return ScaffoldWithNavBar(child: child);
},
....
It is necessary to pass a child inside the ShellRoute, it is not enough to solve it inside otherwise the context is not passed.

Flutter Navigation 2.0 + Bloc

I am currently trying to learn Navigation 2.0 in conjunction with BLoC.
I've followed the raywenderlich's guide [1] successfully (it's a bit outdated) and i tried to move forward managing the state with BLoC (this guide uses Provider) and when i did it successfully, i tried to take a step further and i tried to follow JalalOkbi's guide [2] because of a more advanced abstraction level provided. The third link [3] is the github repo with the full (now failing) project.
But after 5 days of trying i stumbled in several errors and i can't figure this out: i am currently facing this error:
I/flutter (10212): looking for /
I/flutter (10212): found Splash("null", null, null)
I/flutter (10212): looking for /
I/flutter (10212): found Splash("null", null, null)
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building Builder:
The settings getter of a page-based Route must return a Page object. Please set the settings to the Page in the Page.createRoute method.
'package:flutter/src/widgets/navigator.dart':
package:flutter/…/widgets/navigator.dart:1
Failed assertion: line 3361 pos 9: 'entry.route.settings == page'
════════ Exception caught by widgets library ═══════════════════════════════════
A GlobalKey was used multiple times inside one widget's child list.
The relevant error-causing widget was
MaterialApp
lib\main.dart:40
════════════════════════════════════════════════════════════════════════════════
[1] https://www.raywenderlich.com/19457817-flutter-navigator-2-0-and-deep-links
[2] https://medium.com/#JalalOkbi/flutter-navigator-2-0-with-bloc-the-ultimate-guide-6672b115adf
[3] https://github.com/msimoncini90/flutter_navigation_2.0
If your page object is defined like
class SplashPage extends Page {
#override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
builder: (BuildContext context) => const SplashScreen(),
);
}
}
The issue is that there is a missing setting in the page definition, which is what the error is showing. The correct code looks like
class SplashPage extends Page {
#override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (BuildContext context) => const SplashScreen(),
);
}
}
Notice that the settings: this parameter, which solves what the error is describing.

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.

How to navigate in flutter during widget build?

I'm trying to detect that user is no longer authenticated and redirect user to login. This is how I'm doing it
Widget build(BuildContext context) {
return FutureBuilder(
future: _getData(context),
builder: (context, snapshot) {
try {
if (snapshot.hasError && _isAuthenticationError(snapshot.error)) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginView()));
}
Unfortunately doing navigation on build is not working. It throws this error
flutter: setState() or markNeedsBuild() called during build.
flutter: This Overlay widget cannot be marked as needing to build because the framework is already in the
flutter: process of building widgets. A widget can be marked as needing to be built during the build
I cannot just return LoginView widget since parent widget containts app bar and floating button and login view needs to be displayed without these controlls.. I need to navigate.
Is it possible to do it?
Wrap it in Future.microtask. This will schedule it to happen on the next async task cycle (i.e. after build is complete).
Future.microtask(() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginView())
));
Streams in flutter
The usual thing is to use a flow where user changes occur.
When the user logs off, he detects that change and can direct it to another window.
problem here :
snapshot.hasError && _isAuthenticationError(snapshot.error)
Instead of this use OR
snapshot.hasError || _isAuthenticationError(snapshot.error)