Flutter: A FocusNode was used after being disposed - flutter

I have a simple Flutter form inside a stateful widget with a bunch of text fields to capture credit card details. And when I show this form, tap the first field, type something in it, then tap the second field, the focus is not transferred to the second field, the cursor stays in the first one, even though both fields appear focused (border is visible on both) and when I type, it goes in the first field. The only way to type something into the second field is to long tap it as if I wanted to paste something in it. And in the console, I see this:
[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: A FocusNode was used after being disposed.
Once you have called dispose() on a FocusNode, it can no longer be used.
#0 ChangeNotifier._debugAssertNotDisposed.<anonymous closure> (package:flutter/src/foundation/change_notifier.dart:117:9)
#1 ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:123:6)
#2 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:234:12)
#3 FocusNode._notify (package:flutter/src/widgets/focus_manager.dart:1052:5)
#4 FocusManager._applyFocusChange (package:flutter/src/widgets/focus_manager.dart:1800:12)
#5 _rootRun (dart:async/zone.dart:1346:47)
#6 _CustomZone.run (dart:async/zone.dart:1258:19)
#7 _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
The thing is that none of the fields have a focus node-set, so it seems something wrong is happening inside the Flutter form state. Has anybody seen this?
Note that for me, it occurs on the web and mobile alike, but not consistently.
Here is the code of the problematic form.
And here is a screenshot illustrating 2 fields having focus at the same time, and the cursor is stuck in the first one. And when I type something on the keyboard, it's directed at the first field. This is running on a physical iPhone device.
And here is the function that is called as the onPressed of an ElevatedButton to show this form:
Future<void> _addPaymentMethod(BuildContext context) async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
body: CardForm(),
),
),
);
}

I think the problem is using Focus widget.
You should be using FocusNode and pass this FocusNode as one of the named arguments of TextField (your CardCvcFormField).
You can attach a listener to the FocusNode and get the focus visibility.
FocusNode _cvcFocusNode;
#override
void initState() {
super.initState();
_cvcFocusNode = FocusNode();
_cvcFocusNode.addListener(_onCvcFormFieldFocusChanged);
}
void _onCvcFormFieldFocusChanged() {
setState(() => cvcHasFocus = _cvcFocusNode?.hasFocus ?? false);
}
#override
void dispose() {
_cvcFocusNode?.removeListener(_onCvcFormFieldFocusChanged);
_cvcFocusNode?.dispose();
_cvcFocusNode = null;
super.dispose();
}

Upgrade Flutter version currently its working fine with 3.7.2

Related

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.

Issue with scanning QR codes using flutter

Hi I'm trying to read a QR code and send the data in the QR code to my server. But when running flutter build ios I get the error in xcode when launching the app:
LateInitializationError: Field '_channel#598294394' has not been initialized.
#0 _QRViewState._channel (package:qr_code_scanner/src/qr_code_scanner.dart)
#1 _QRViewState.updateDimensions (package:qr_code_scanner/src/qr_code_scanner.dart:91:57)
#2 LifecycleEventHandler.didChangeAppLifecycleState (package:qr_code_scanner/src/lifecycle_event_handler.dart:15:29)
#3 WidgetsBinding.handleAppLifecycleStateChanged (package:flutter/src/widgets/binding.dart:692:16)
#4 ServicesBinding._handleLifecycleMessage (package:flutter/src/services/binding.dart:192:5)
#5 BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:73:49)
#6 BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:72:47)
#7 _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binding.dart:284:33)
#8 _invoke3.<anonymous closure> (dart:ui/hooks.dart:223:15)
#9 _rootRun (dart:async/zone.dart:1354:13)
#10 _CustomZone.run (dart:async/zone.dart:1258:19)
#11 _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
#12 _invoke3 (dart:ui/hooks.dart:222:10)
#13 PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:520:7)
#14 _dispatchPlatformMessage (dart:ui/hooks.dart:90:31)
The same thing happens when running flutter run
But that is fixed by doing a hot restart. Does anybody know why this is happening?
When this happens the app refuses to read any QR codes.
You can find my code on github here: https://github.com/maxall41/Package-Scanner
This is happening because you are calling setState before your widget has fully initialized itself. You can't call set state before the build method has finished because there is nothing to set the state of.
When you do a hot restart the phone (or emulator) keeps the state of the page or widget and rebuilds the ui. At that point, the build method of the widget gets called again and because your set state is in your build method, it is also getting called again, but this time on a state that was already initialized.
As a side note: Please post the relevant code instead of a link to your github repo. It helps you get better answers and it makes this a more useful question/ answer to the community
Edit: Here is the block of code causing problems.
Widget _buildQrView(BuildContext context) {
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
);
}
void _onQRViewCreated(QRViewController controller) {
//Here's the setState in the build method
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) async {
setState(() {
result = "Scanned: " + scanData.code;
});
});
}
Removing the setState surrounding this.controller = controller should solve the problem

ScaffoldMessenger throws a hero animation error

I am using the new ScaffoldMessenger to show a snackbar if a user successfully creates a project.
While showing the snackbar, i navigate the app to the dashboard. But as soon as it hits the dashboard There are multiple heroes that share the same tag within a subtree error is thrown.
I am not using any Hero widget in my dashbard and I have one FloatingActionButton but its hero parameter is set to null.
Sample code:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('A SnackBar has been shown.'),
animation: null,
),
);
Navigator.pushReplacementNamed(context, '/dashboard');
Which results in this error:
The following assertion was thrown during a scheduler callback:
There are multiple heroes that share the same tag within a subtree.
Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must have a unique non-null tag.
In this case, multiple heroes had the following tag: <SnackBar Hero tag - Text("A SnackBar has been shown.")>
Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must have a unique non-null tag.
In this case, multiple heroes had the following tag: <SnackBar Hero tag - Text("A SnackBar has been shown.")>
Here is the subtree for one of the offending heroes: Hero
tag: <SnackBar Hero tag - Text("A SnackBar has been shown.")>
state: _HeroState#7589f
When the exception was thrown, this was the stack
#0 Hero._allHeroesFor.inviteHero.<anonymous closure>
#1 Hero._allHeroesFor.inviteHero
package:flutter/…/widgets/heroes.dart:277
#2 Hero._allHeroesFor.visitor
package:flutter/…/widgets/heroes.dart:296
#3 ComponentElement.visitChildren
package:flutter/…/widgets/framework.dart:4729
#4 Hero._allHeroesFor.visitor
package:flutter/…/widgets/heroes.dart:309
...
I had the same problem. This happens if you have nested Scaffolds. The ScaffoldMessenger wants to send the Snackbar to all Scaffolds. To fix this you need to wrap your Scaffold with a ScaffoldMessenger. This ensures you that only one of your Scaffold receives the Snackbar.
ScaffoldMessenger(
child: Scaffold(
body: ..
),
)
I ran into same problem and fixed it by removing SnackBar before any call to Navigator with ScaffoldMessenger.of(context).removeCurrentSnackBar().
Look like this with your Sample code:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('A SnackBar has been shown.'),
animation: null,
),
);
ScaffoldMessenger.of(context).removeCurrentSnackBar();
Navigator.pushReplacementNamed(context, '/dashboard');
Here's the link that helped me : https://flutter.dev/docs/release/breaking-changes/scaffold-messenger#migration-guide
Hope it'll work for you
I resolved this by having the call run in the next event-loop iteration:
Future.delayed(const Duration(), () =>
ScaffoldMessenger.of(context).showSnackBar(SnackBar(...)));
Had the same problem, turns out I had a scaffold widget returning another scaffold in my subtree (whoops)
If so, then your snackbar is being popped on both scaffolds, and then initiating the transition causes the error.
So this is clearly a bug in Flutter.
The best "official" workaround, is to show the snack bar on the next frame:
WidgetsBinding.instance.addPostFrameCallback((_) {
// ... show the culprit SnackBar here.
});
But we can all agree that it shouldn't happen in the first place.
The answer by #GreenFrog assumes that you're handling navigation on your own, in the case you're facing this problem while using the default back button behavior of the Scaffold widget you'll need to wrap your Scaffold in a WillPopScope widget, this basically vetos requests to Navigator, moreover by using WillPopScope.onWillPop you can essentially call ScaffoldMessenger.of(context).removeCurrentSnackBar(); just before the route is popped.
Example:
...
WillPopScope(
onWillPop: () async {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
return true;
},
child: Scaffold(...,
);

Using Navigator inside setState

I have a list of Strings (called questions).
I create a Text widget based on the current string in the list.
I have an index int that increases every time a button is pressed.
I increase the current index by 1 in the setState method.
I need to navigate to a different page when the current index reaches the length of the String list.
Otherwise, I will get an RangeError naturally.
setState(() {
this.currentIndex++;
if(this.currentIndex == questions.length) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Loser()),
);
}
});
Now based on the code above, the error page appears and disappears quickly.
It is replaced by the Loser() page quickly.
Why is that?
And how can I navigate to the Loser() page without the error page showing?
Edit: As requested, the error message:
════════ Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building LandingPage(dirty, state: _LandingPageState#a8efe):
RangeError (index): Invalid value: Not in inclusive range 0..10: 11
The relevant error-causing widget was
LandingPage
lib/main.dart:21
When the exception was thrown, this was the stack
#0 List.[] (dart:core-patch/growable_array.dart:153:60)
#1 _LandingPageState.build
package:testing_http_package/landing_page.dart:88
#2 StatefulElement.build
package:flutter/…/widgets/framework.dart:4628
#3 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4511
#4 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:4684
...
════════════════════════════════════════════════════════════════════════════════
Edit: The widget I think in the build method that is causing the error:
child: Center(
child: Text(
questions[currentIndex], // This line
style: style,
textAlign: TextAlign.center,
),
),
),
Shouldn't the setState method go straight to the page before rerunning the build method?
Edit: I added the didChangeDependencies method as per #Nuts suggestion but it did not work. Now only the error page appears and it does not proceeds to the other page:
#override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if(this.currentIndex == questions.length) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Loser()),
);
}
});
super.didChangeDependencies();
}
With setState - you are rebuilding the whole widget and while doing it - navigating. So you are trying to rebuild widgets with invalid params (in your case index)
this.currentIndex++;
if(this.currentIndex => questions.length) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Loser()),
);
}
else setState(() {}); // if currentIndex is valid, just rebuild

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