skip to Main Content

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


  1. Chosen as BEST ANSWER

    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.

    void disposeLeaveRoomSuccessListener(BuildContext context) {
        _socketClient.off('leaveRoomSuccess');
      }
    

    Then in the widget tree

      @override
      void dispose() {
        _socketMethods.disposeLeaveRoomSuccessListener(context);
        super.dispose();
      }
    

  2. 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.

    if (context.mounted) {
      // Access the widget or its state
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search