Memory leak when using setState to overwrite value flutter - flutter

I have some problem. When I try to overwrite a value using setState, infinite loop happening and a warning about memory leaks. I dont know why its happening, and I tried to put if(mounted), but its still not working. Any advice for this?
ListView.separated(
separatorBuilder: (BuildContext context, int i) => Divider(color: Colors.grey[400]),
itemCount: listJadwalDokter.length,
itemBuilder: (context, index) {
JadwalDokter jadwal = listJadwalDokter[index];
if(jadwal.hari < now.weekday)
for(int test = 0;test <jadwal.jadwalPraktek.length; test++) {
cutiService.cekCuti(jadwal.kodeDokter + "." + listTanggalFormatKode[jadwal.hari + 7] + jadwal.jadwalPraktek[test].jam.substring(0, 2)).then((value) {
if (value)
if(mounted)
setState(() {
//when infinite loop happened
jadwal.jadwalPraktek[test].jam =jadwal.jadwalPraktek[test].jam.substring(0, 11) + "\n(Sedang Cuti)";
});
});
}
}
)
This ListView is inside an AlertDialog that wrapped in StatefullBuilder so it can update the view when setState is called.
This is the error log
E/flutter ( 6791): 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.
E/flutter ( 6791): 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.
E/flutter ( 6791): 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().

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 + Bloc 6.0.6. BlocBuilder's "builder" callback isn't provided with the same state as the "buildWhen" callback

I'm building a tic-tak-toe app and I decided to learn BLoC for Flutter along. I hava a problem with the BlocBuilder widget.
As I think about it. Every time Cubit/Bloc that the bloc builder widget listens to emits new state the bloc builder goes through this routine:
Call buildWhen callback passing previous state as the previous parameter and the newly emitted state as the current parameter.
If the buildWhen callback returned true then rebuild.
During rebuilding call the builder callback passing given context as context parameter and the newly emitted state as state parameter. This callback returns the widget that we return.
So the conclusion is that the current parameter of the buildWhen call is always equal to the state parameter of the builder call. But in practice it's different:
BlocBuilder<GameCubit, GameState>(
buildWhen: (previous, current) => current is SetSlotSignGameState && (current as SetSlotSignGameState).slotPosition == widget.pos,
builder: (context, state) {
var sign = (state as SetSlotSignGameState).sign;
// Widget creation goes here...
},
);
In the builder callback, it throws:
The following _CastError was thrown building BlocBuilder<GameCubit, GameState>(dirty, state:
_BlocBuilderBaseState<GameCubit, GameState>#dc100):
type 'GameState' is not a subtype of type 'SetSlotSignGameState' in type cast
The relevant error-causing widget was:
BlocBuilder<GameCubit, GameState>
The method where I emit the states that is in the GameCubit class:
// [pos] is the position of the slot clicked
void setSlotSign(Vec2<int> pos) {
// Some code
emit(SetSlotSignGameState(/* Parameter representing the sign that is being placed in the slot*/, pos));
// Some code
emit(TurnChangeGameState());
}
Briefly about types of states. SetSlotSignGameState is emitted when a user taps on a slot in the tic-tac-toe grid and the slot is empty. So this state means that we need to change sign in some slot. TurnChangeGameState is emitted when we need to give the turn to the next player.
Temporary solution. For now I fixed it by saving the state from buildWhen callback in a private field of the widget's state and then using it from the builder. BlocListener also has this problem but there I can just move the check from listenWhen callback into listen callback. The disadvantage of this solution is that it's very inelegant and inconvenient.
buildWhen is bypassed (not even called) on initial state OR when Flutter requests a rebuild.
I have created a small "test" to emphasize that:
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(BlocTestApp());
}
class BlocTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider<TestCubit>(
// Create the TestCubit and call test() event right away
create: (context) => TestCubit()..test(),
child: BlocBuilder<TestCubit, String>(
buildWhen: (previous, current) {
print("Call buildWhen(previous: $previous, current: $current)");
return false;
},
builder: (context, state) {
print("Build $state");
return Text(state);
},
),
),
);
}
}
class TestCubit extends Cubit<String> {
TestCubit() : super("Initial State");
void test() {
Future.delayed(Duration(seconds: 2), () {
emit("Test State");
});
}
}
OUTPUT:
I/flutter (13854): Build Initial State
I/flutter (13854): Call buildWhen(previous: Initial State, current: Test State)
As can be seen from output the initial state is built right away without calling buildWhen. Only when the state changes buildWhen is examined.
Other References
This behavior is also outlined here by the creator of Flutter Bloc library (#felangel):
This is expected behavior because there are two reasons for a
BlocBuilder to rebuild:
The bloc state changed
Flutter marked the widget as needing to be rebuilt.
buildWhen will prevent builds triggered by 1 but not by 2. In
this case, when the language changes, the whole widget tree is likely
being rebuilt which is why the BlocBuilder is rebuilt despite
buildWhen.
Possible solution
In your situation, based on the little code you revealed, is better to store the entire Tic-Tac-Toe configuration in the state and use BLOC events to alter it. In this way you do not need that buildWhen condition.
OR make the check inside the builder function if the logic let you do that (this is the most common used solutions with BLOC).
To respond to you question (if not clear so far :D): Sadly, you can not rely on buildWhen to filter the state types sent to builder function.
Could you please check if SetSlotSignGameState extends the abstract class GameState

How to perform setState() while listening to a Stream

I hope you are doing well. I was looking for a way where I can perform setState within a Stream. Basically what I am doing here is, I am getting the data from the collection, assigning it to a List variable called currentDocuments and I am displaying it in a ListView.builder(). I did not use StreamBuilder because, the documents can be deleted, but have to be deleted locally and not from firestore.
The problem is when I add a new document to 'mycollection' collection, I get this message:
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().
I have tried a lot of ways using 'mounted' field, but still, I get the message mentioned above. Any help regarding this issue would be greatly appreciated. Thank you in advance.
Code:
_subscription = Firestore.instance.collection('mycollection').snapshots().listen((QuerySnapshot querySnapshot) {
for(var i in querySnapshot.documentChanges){
if(i.type == DocumentChangeType.added){
if(!mounted){
setState((){});
}
setState(() {
currentDocuments = querySnapshot.documents;
});
}
}
});
_subscription = Firestore.instance.collection('mycollection').snapshots().listen((QuerySnapshot querySnapshot) {
for(var i in querySnapshot.documentChanges){
if(i.type == DocumentChangeType.added){
if(mounted){
setState(() {
currentDocuments = querySnapshot.documents;
});
}
}
}
});
here mounted will be true if the widget exists in the tree, but in your code there are two setState calls which will fire either the mounted is true or not!!, use/call setState only when mounted is true,

Flutter - How to dynamically add height value of a container before loading the UI?

I have added a setState Method inside the build widget after getting my data from API response via StreamBuilder. But it gives this error:
Unhandled Exception: setState() or markNeedsBuild() called during build.
How do I avoid this situation?
My code:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: StreamBuilder(
stream: bloc.getData,
builder: (context, AsyncSnapshot<Home> dataSnapshot) {
if (dataSnapshot.hasData) {
if (dataSnapshot.data.description != null) _expandHeightBy(40);
........
Function
_expandHeightBy(double increase) async {
setState(() {
_expandedHeightVal += increase;
});
}
It is not possible to call setState during build, and for good reason. setState runs the build method. Therefore if you were able to call setState in build, it would infinitely loop the build method since it essentially calls itself.
Check out this article. It has an example of conditionally rendering based on a StreamBuilder. https://medium.com/#sidky/using-streambuilder-in-flutter-dcc2d89c2eae
Removed the setState from the method calling as #CodePoet suggested and it actually worked fine.
_expandHeightBy(double increase) {
_expandedHeightVal += increase;
}