Unhandled Exception: setState() called after dispose() with Firebase Realtime Database chat feature - flutter

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

Related

Flutter Difference between InitState and just putting inside Widget Build function

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.

Looking up a deactivated widget's ancestor is unsafe on call from Provider flutter

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

Get a provider method in the dispose method not working

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

Flutter initState not being called

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.

Unhandled Exception: inheritFromWidgetOfExactType(_LocalizationsScope) or inheritFromElement() was called before _ScreenState.initState() completed

I am calling initial method to load data from API using initState. But it is resulting me an error. Here is error:
Unhandled Exception: inheritFromWidgetOfExactType(_LocalizationsScope) or inheritFromElement() was called before _ScreenState.initState() completed.
When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget.
My code is:
#override
void initState() {
super.initState();
this._getCategories();
}
void _getCategories() async {
AppRoutes.showLoader(context);
Map<String, dynamic> data = await apiPostCall(
apiName: API.addUser,
context: context,
parameterData: null,
showAlert: false,
);
if(data.isNotEmpty){
AppRoutes.dismissLoader(context);
print(data);
}else {
AppRoutes.dismissLoader(context);
}
}
You need to call _getCategories after initState has completed.
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
this._getCategories();
});
// Could do this in one line: Future.delayed(Duration.zero, this._getCategories);
}
Also, you could do this on a different way, using addPostFrameCallback.
To make this task easier, you could create a mixin to be added to StatefulWidgets.
mixin PostFrameMixin<T extends StatefulWidget> on State<T> {
void postFrame(void Function() callback) =>
WidgetsBinding.instance?.addPostFrameCallback(
(_) {
// Execute callback if page is mounted
if (mounted) callback();
},
);
}
Then, you just need to plug this mixin to you page, like that:
class _MyPageState extends State<MyPage> with PostFrameMixin {
#override
void initState() {
super.initState();
postFrame(_getCategories);
}
}
Use the didChangeDependencies method which gets called after initState.
For your example:
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
this._getCategories();
}
void _getCategories() async {
// Omitted for brevity
// ...
}
Adding a frame callback might be better than using Future.delayed with a zero duration - it's more explicit and clear as to what is happening, and this kind of situation is what frame callback was designed for:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
_getCategories();
});
}
an alternative is to put it inside PostFrameCallback which is between initState and Build.
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) => getData());
super.initState();
}
getData() async {
}
There are many ways to solve this problem, override initState method:
#override
void initState() {
super.initState();
// Use any of the below code here.
}
Using SchedulerBinding mixin:
SchedulerBinding.instance!.addPostFrameCallback((_) {
// Call your function
});
Using Future class:
Future(() {
// Call your function
});
Using Timer class:
Timer(() {
// Call your function
});
The best solution i think is use the context from the Widget build. And paste the method _getCategories(context) after the build with the context from the tree.
So there is no problem with the widget tree.