If I try and update the stateProvider by overriding the widgets dispose method I get an exception as shown below. Any idea how to update the state, so that the next time this widget is rebuilt it's using the state of null:
Code:
#override
void dispose() {
//reset any request map initial bounds
ref.read(fsdMapStateProvider.notifier).state = null;
super.dispose();
}
Error:
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown while finalizing the widget tree:
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.
example of how you can use addPostFrameCallback to update a StateProvider when disposing a widget
#override
void dispose() {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(fsdMapStateProvider.notifier).state = null;
});
super.dispose();
}
we are scheduling a callback to set the state of the fsdMapStateProvider to null after the current frame has been drawn. This ensures that the widget tree will still be stable when the state is updated.
Related
I am receiving this error:
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: setState() called after dispose(): _EventChatScreenState#7c8b5(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/wid<…>
However, the only place I am calling setState is in the initState:
#override
void initState() {
super.initState();
dbRef = dbInstance.ref("/events/");
var query = dbRef!.child(widget.event.event.eventId);
FirebaseList(
query: query,
onChildAdded: (index, snapshot) {
Map<dynamic, dynamic> childMap = snapshot.value as dynamic;
ChatMessage newChatMessage = ChatMessage(
chatMessageId: snapshot.key.toString(),
userId: childMap["userId"],
displayName: childMap["displayName"],
message: childMap["message"],
datetime: childMap["datetime"],
);
setState(() {
chatMessages.add(newChatMessage);
});
},
);
_messageFieldController = TextEditingController();
}
#override
void dispose() {
super.dispose();
_messageFieldController.dispose();
}
I'm not really sure why this is happening, but I included the dispose method since it the error references it.
Worth noting that I am doing this to make the screen scroll to the bottom of the chat messages which are display using a ListView.builder
void scrollToBottom() {
SchedulerBinding.instance.addPostFrameCallback((_) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
});
}
#override
Widget build(BuildContext context) {
final user = ref.watch(userProvider);
if (chatMessages.isNotEmpty) {
scrollToBottom();
}
If I remove the above code the issue seems to go away
instead of this
#override
void dispose() {
super.dispose();
_messageFieldController.dispose();
}
try this
#override
void dispose() {
_messageFieldController.dispose();
super.dispose();
}
I had an error every time I restarted my App: This widget has been unmounted, so the State no longer has a context (and should be considered defunct). and saw that something was not correct with my initstate. The initState was:
#override
void initState() {
SchedulerBinding.instance.addPostFrameCallback((_) {
BlocProvider.of<TutSkippedCubit>(context).load();
});
super.initState();
}
the methods loads the data from sharedprefs if I have already skipped the tut or not. Now I solved this issue with removing the initState method and putting the function call inside the widget build:
#override
Widget build(BuildContext context) {
BlocProvider.of<TutSkippedCubit>(context).load();
....
The widget build gets called when the pages loads, so, isn't it the same as the initial state? For what exactly is the methode initState() and I have the feeling that my way of handling this problem is a bad practise, but what would be a better way, how do I solve it?
The initState() method is to control what happens after the app is built. The problem is that you call BlocProvider before the app begins. The correct way is to put all the actions after super.initState() call and add the context to the BlocProvider inside build method. Like this:
TutSkippedCubit? tutSkippedCubitProvider;
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
tutSkippedCubitProvider!.load();
});
}
#override
Widget build(BuildContext context) {
tutSkippedCubitProvider = BlocProvider.of<TutSkippedCubit>(context);
...
}
The initState and build method is called when the widget is inserted into the widget tree, but the build method also is called every time the state is changed.
You do need to have in mind that every time the state is changed your method BlocProvider.of<TutSkippedCubit>(context).load(); also is called.
Maybe, the code below can help you:
WidgetsBinding.instance.endOfFrame.then(
(_) async {
if (mounted) {
BlocProvider.of<TutSkippedCubit>(context).load();
}
},
);
You wouldn't be surprise of getting that error since you are using BlocProvider.<T>(context) out of a BuildContext. This context in bracket is the just the same as the one given in the build function.
The initState() is a method that is called when an object for your
stateful widget is created and inserted inside the widget tree.
This issue is weird in that upon fully restarting the app, it works fine until you navigate to the CreateJoinPage, then navigate back to the homepage and then navigate forward to CreateJoinPage and then try to execute the block of code, the error message will start to pop up. I am creating a listener in CreateJoinPage that listens for input from socket.io:
void createRoomSuccessListener(BuildContext context) {
_socketClient.on('createRoomSuccess', (room2) {
Provider.of<RoomDataProvider>(context, listen: false)
.updateRoomData(room);
Provider.of<RoomDataProvider>(context, listen: false).insertPlayer(room);
Navigator.push(
context,
MaterialPageRoute(
builder: (contextPage) => const ActivitySelectionPage()),
);
});
}
and this error is called from either of the two Provider.of lines being part of the code.
This listener is called from my CreateJoinPage(StatelessWidget) as such:
SocketMethods().createRoomSuccessListener(context);
The full error code is:
Error: 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.
It looks like the problem is that the context gets destroyed in the meantime a socket event arrives. To fix that the listener should be removed if the CreateJoinPage widget is disposed. First, change the CreateJoinPage from StatelessWidget to StatefulWidget to override the disposed method like so:
void removeRoomSuccessListener() {
_socketClient.off('createRoomSuccess');
}
class CreateJoinPage extends StatefulWidget {
#override
State<CreateJoinPage> createState() => _CreateJoinPageState();
...
}
class _CreateJoinPageState extends State<CreateJoinPage> {
#override
void dispose() {
SocketMethods().removeRoomSuccessListener(context);
super.dispose();
}
...
}
And declare the SocketMethods.removeRoomSuccessListener like so:
void removeRoomSuccessListener() {
_socketClient.off('createRoomSuccess');
}
I'm trying to access a provider method in the dispose function.
#override
void dispose() {
if (canRemoveData) Provider.of<MyProvider>(context, listen: false).clearData();
super.dispose();
}
but when that gets called I get the error:
The following assertion was thrown while finalizing the widget tree:
Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer
stable.
What am I doing wrong and how can I fix it?
The error description basically says it all. The dispose() method is intended for disposing your widget's state dependencies and controllers, so you shouldn't try to find ancestors of this widget at this point because your widget is already deleted from the widget tree.
You should instead make a variable in your state and provide MyProvider value in initState(). Then you would be able to call MyProvider#clearData() from dispose as such:
class _MyWidgetState extends State<MyWidget> {
MyProvider _myProvider;
#override
void initState() {
super.initState();
_myProvider = Provider.of<MyProvider>(context, listen: false);
}
#override
void dispose() {
if (canRemoveData) _myProvider.clearData();
super.dispose();
}
#override
Widget build(BuildContext context) {
// implement
}
}
Also if you provide MyProvider as a direct parent of your widget and after it's dispose you wouldn't need MyProvider anywhere else, you could use ProxyProvider dispose method like that:
ProxyProvider<SomeDependency, MyProvider>(
update: (context, someDependency, previous) => previous ?? MyProvider(someDependency),
dispose: (context, myProvider) => myProvider.clearData(),
),
you can override deactivate method in StatefullWidget
#override
void deactivate() {
super.deactivate();
context.read<PinScreenProvider>().destroy();
}
Note: destroy is a function, where i written clear data
When I open a chat page as a pop up the initState gets called the first time, but when I use Navigator.pop(context) and then open the chat page again the initState does not get called and I get an error for my StreamSubscription -> The method 'cancel' was called on null. But I do initialize it in the initState.
Why isn't the initState being called THE SECOND time I open the chat page, WHEN THE FIRST TIME I OPEN IT IT WORKS PERFECTLY?
// ignore: cancel_subscriptions
StreamSubscription _streamSubscription;
#override
void initState() {
if (this.chat != null) if (widget.chat.messages.isEmpty)
this._prevMessages().then((value) {
this._initMessages(); // <-- WHERE I'M INITIALIZING THE StreamBuilder
this._scrollToBtm();
});
super.initState();
}
#override
void dispose() {
this._streamSubscription.cancel(); // <-- THE ERROR
this._scrollController.dispose();
this._msgTEC.dispose();
this._msgFN.dispose();
super.dispose();
}
_initMessages() {
Funcs.log('Init Live Messages...');
this._streamSubscription = APIs().chats.messagesLive(...);
}
The exact log:
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The method 'cancel' was called on null.
Receiver: null
Tried calling: cancel()
Make sure that you initialize _streamSubscription before invoking it asynchronosuly. There must be some default value set. What's happening is that where you are calling _initMessages(), it is being invoked inside a Future,which means it is invoked after (or not, it could be at anytime) initialization completes.
You can either make sure to await this._prevMessages(), thus:
#override
void initState() async {
if (this.chat != null) if (widget.chat.messages.isEmpty)
await this._prevMessages().then((value) {
this._initMessages(); // <-- WHERE I'M INITIALIZING THE StreamBuilder
this._scrollToBtm();
});
super.initState();
}
Or you can initialize the _streamSubscription object before running the initialization code. There must be some default value set here.
Also the dispose method should not be invoked before initialization. This means that you have code somewhere that is disposing the object the moment that the item gets initialized.