Flutter Navigation from Firestore Flag - flutter

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.

Related

Prevent memory leak in Flutter

I was trying to make my function works with a Timer to fetch a list of actions from Database and display them without leaving the screen and re-enter again.
I tried the following :
void initState() {
super.initState();
Timer.periodic(timeDelay, (Timer t) => fetchFournisseurs());
}
It's working now , but my debug console is showing some infos :
State.setState.<anonymous closure> (package:flutter/src/widgets/fr<…>
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: setState() called after dispose(): _listeDocumentState#c0f9f(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().
Is there a better way to make it and prevent memory leak or anything bad ?
Thank you.
All of the information is in the error message itself.
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).
For example if you navigate to a page containing this widget, and then close that page, the widget would no longer be in the widget tree, but the Timer you created is still running periodically.
This error can occur when code calls setState() from a timer or an animation callback.
Presumably fetchFournisseurs is calling setState.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback.
In other words your state class should do something along these lines.
class _ExampleState extends State<Example> {
final Duration timeDelay = ...;
// Create a variable to hold the timer.
Timer? _timer;
#override
void initState() {
super.initState();
// Assign timer to the variable.
_timer = Timer.periodic(timeDelay, (Timer t) => fetchFournisseurs());
}
#override
void dispose() {
// Cancel the timer in the dispose() callback.
_timer?.cancel();
super.dispose();
}
void fetchFournisseurs() {
// presumably fetchFournisseurs calls setState at some point.
setState(() {
...
});
}
#override
Widget build(BuildContext context) {
return ...;
}
}
Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
Which means to do the following.
void fetchFournisseurs() {
// check if the widget is mounted before proceeding
if (!mounted) return;
setState(() {
...
});
}
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().
Another reminder to cancel the Timer in dispose to avoid memory leaks.

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.

Flutter : How to destroy all the API call (Future and Stream) when dispose a page

Switching bottom navigation bar faster from Tab1 to Tab2. When switching to Tab2 while the tab 1 API call is still running, the log file still display the result from Tab1 API call when I'm inside Tab2. So what I want to do now is whenever I dispose a page it will dispose everything.
I've tried CancelableOperation.fromFuture method and I put the CancelableOperation cancel inside dispose function, the log file show The method 'cancel' was called on null.
CancelableOperation cancellableOperation = CancelableOperation.fromFuture(
Future.value(await getApiData()),
onCancel: () =>
{debugPrint('onCancel')},
);
#override
void dispose() {
cancellableOperation.cancel();
super.dispose();
}
I/flutter ( 9421): Another exception was thrown: NoSuchMethodError: The method 'cancel' was called on null.
Any idea how to resolve this or any better solution ?

Flutter Socket IO can't get data after tab is changed

So, When I open the tab for the first time, data from Socket.IO appears. But when I change the tab and go back, I can't get data from Socket.IO.
This is my Code:
Map <String,dynamic> list;
IO.Socket socket = IO.io('http://localhost:3000', <String, dynamic>{
'transports': ['websocket']
});
#override
initState(){
socket.on('connect',(_){
socket.on('stockQuote',(jsonData){
setState(() {
list = jsonData;
isLoading = false;
});
});
});
super.initState();
}
dispose(){
super.dispose();
}
I get these errors:
Unhandled Exception: setState() called after dispose(): _StocksState#0faab(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()
So apparently I found a solution to make the Socket.io load again.
Fix Code:
IO.Socket socket = IO.io('http://localhost:3000', <String, dynamic>{
'transports': ['websocket'],
'forceNew':true
});
So apparently I need to add 'forceNew': true so that the page loads the socket.io like new.
Source: How to close a socket.io connection

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