Hi I’m trying to learn flutter by creating an online game room. MongoDb is used to store the information of the players and rooms. I’m able to have 2 devices connected to the same room, however when 1 device creates a room, then leaves, I get the error below. Is there a reason why this happens? I’m suspecting it could be the navigator.pop(context) in socket_method.dart, but I don’t know how to fix it.
E/flutter ( 4526): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 4526): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 4526): 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.
My codes below:
socket_method.dart:
void leaveRoom(String roomId) {
_socketClient.emit('leaveRoom', {'roomId': roomId});
}
void leaveRoomSuccessListener(BuildContext context) {
_socketClient.on('leaveRoomSuccess', (data) {
if (data['room'] != null) {
Provider.of<RoomDataProvider>(context, listen: false)
.updateRoomData(data['room']);
Navigator.pop(context);
showSnackBar(context, data['msg']);
}
});
}
index.js
socket.on('leaveRoom', async({roomId})=>{
try{
console.log(`leaving room ${roomId}`);
let room = await Room.findOne({roomId: roomId});
if(room){
console.log(room)
console.log(socket.id)
}
else{
console.log('room not found')
}
const index = room.players.map((player) => player.socketId).indexOf(socket.id)
console.log(index)
const deletedPlayer = room.players[index];
if (index > -1) { // only splice array when item is found
room.players.splice(index, 1); // 2nd parameter means remove one item only
}
room.occupancy = room.players.length;
// if room is empty, delete room
if(room.occupancy === 0 || deletedPlayer.playerType === 'host'){
io.to(roomId).emit('leaveRoomSuccess', {room, msg: 'Room has been closed'});
await Room.deleteOne({roomId: roomId});
}
// else, update room
else{
room = await room.save();
socket.leave(roomId);
io.to(socket.id).emit('leaveRoomSuccess', {room, msg: 'You have left the room'});
}
}
catch(err){
console.log(err);
}
})
create_room_screen.dart:
class CreateRoomScreen extends StatefulWidget {
static String routeName = '/create-room';
const CreateRoomScreen({super.key});
@override
State<CreateRoomScreen> createState() => _CreateRoomScreenState();
}
class _CreateRoomScreenState extends State<CreateRoomScreen> {
final TextEditingController _nameController = TextEditingController();
final SocketMethods _socketMethods = SocketMethods();
@override
void initState() {
super.initState();
_socketMethods.createRoomSuccessListener(context);
_socketMethods.checkStatus();
}
//prevent memory leaks
@override
void dispose() {
super.dispose();
_nameController.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
body: Responsive(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CustomText(text: 'Create Room', fontSize: 70, shadows: [
Shadow(
blurRadius: 20,
color: Colors.blue,
)
]),
SizedBox(height: size.height * 0.1),
CustomTextField(
controller: _nameController,
hintText: "Enter a name",
),
SizedBox(height: size.height * 0.045),
CustomButton(
onPressed: () => {
_socketMethods.createRoom(_nameController.text),
},
text: "Create")
]),
),
),
);
}
}
game_room_screen.dart
class GameScreen extends StatefulWidget {
static String routeName = '/game';
const GameScreen({super.key});
@override
State<GameScreen> createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
final SocketMethods _socketMethods = SocketMethods();
@override
void initState() {
super.initState();
_socketMethods.leaveRoomSuccessListener(context);
}
@override
void didChangeDependencies(){
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final roomId = Provider.of<RoomDataProvider>(context).roomData['roomId'];
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(Provider.of<RoomDataProvider>(context).roomData.toString()),
CustomButton(
onPressed: () => {_socketMethods.leaveRoom(roomId)},
text: "Leave Room")
],
)),
);
}
}
2
Answers
The registered listener in leaveRoomSuccessListener wasn't unregister when the widget was disposed.
Creating a function to turn off the listener then dispose it in the widget solved this issue. I'm not an expert in this but I hope this can help someone.
Then in the widget tree
The "Looking up a deactivated widget’s ancestor is unsafe" error in Flutter occurs when you try to access the state of a widget that has been deactivated or disposed. This typically happens when you are trying to reference a widget that is higher up in the widget tree but has already been removed or is no longer active.
In Flutter, widgets have a lifecycle, and when a widget is removed from the widget tree, it goes through a series of cleanup processes, including deactivation and disposal. During these processes, the widget and its associated state are removed, and any attempts to access them can lead to this error.
Solution:
Use mounted property: Widgets have a mounted property that indicates whether they are currently active in the widget tree. You can check this property before accessing a widget or its state to ensure it is still mounted.