socket.io-client-dart icon indicating copy to clipboard operation
socket.io-client-dart copied to clipboard

Double connection?

Open Elevencode opened this issue 2 years ago • 2 comments

I created the SocketService class. Inside it there is a method createSocketConnection(), which creates a connection and subscribes to events. The problem is that after Hot Restart the subscription is not closed, if I understand correctly.

My SocketService code:

class SocketService {
  late sio.Socket socket;
  Map<String, StreamController<String>> messageControllers = {};
  Map<String, String> messageTexts = {};

  SocketService() {
    createSocketConnection();
  }

  void disconnect() {
    socket.disconnect();
  }

  void createSocketConnection() {
    socket = sio.io(
      AppConfig.socketUrl,
      <String, dynamic>{
        'transports': ['websocket'],
        'reconnection': true,
        'reconnectionAttempts': 5,
        'reconnectionDelay': 5000,
        'autoConnect': false,
        'enableForceNewConnection': false,
      },
    );

    socket.connect();

    socket.on('connect', (_) => print('sio: connection success with id ${socket.id}'));
    socket.on('connect_error', (data) => print('sio: connection error: $data'));
    socket.on('connect_timeout', (data) => print('sio: connection timeout: $data'));
    socket.on('reconnect_failed', (_) => print('sio: reconnect failed'));
    socket.on('new_token', (data) => _tokenHandler(data));
    socket.on('message_end', (data) => _messageEndedHandler(data));
    socket.on('shutdown', (_) => socket.disconnect());
  }

  void sendMessage(String eventType, Map<String, String> data) {
    String messageId = data['message_id'] ?? '';
    messageControllers[messageId] = StreamController<String>.broadcast();
    try {
      socket.emit(eventType, data);
    } catch (e) {
      print(e.toString());
    }
  }

  void receiveMessage(
    String messageType,
    dynamic Function(dynamic) callback,
  ) =>
      socket.on(messageType, callback);

  void _tokenHandler(dynamic data) {
    Map<String, dynamic> tokenData = data as Map<String, dynamic>;
    String token = tokenData['token'] ?? '';
    String messageId = tokenData['message_id'] ?? '';

    messageTexts[messageId] = (messageTexts[messageId] ?? '') + token;

    messageControllers[messageId]?.add(messageTexts[messageId]!);
  }

  Stream<String> tokenStream(String messageId) {
    return messageControllers[messageId]?.stream ?? const Stream.empty();
  }

  void _messageEndedHandler(Map<String, dynamic> data) {
    String messageId = data['ended_message_id'] ?? '';

    print('Message ended: $messageId');
    messageControllers[messageId]?.close();
    messageControllers.remove(messageId);
    messageTexts.remove(messageId);
  }

  void dispose() {
    print('Disposing SocketService');

    socket.off('connect');
    socket.off('connect_error');
    socket.off('connect_timeout');
    socket.off('reconnect_failed');
    socket.off('new_token');
    socket.off('message_end');

    for (var controller in messageControllers.values) {
      controller.close();
    }
    messageControllers.clear();

    socket.disconnect();
    socket.destroy();
  }
}

When the application is first started, at the moment when the message_end event is triggered - everything is ok, the _messageEndedHandler method is triggered once. However, after Hot Restart, the method is triggered 2 times. If I do Hot Restart again, the method triggers 3 times.

I am initializing my class as singleton using GetIt:

sl.registerLazySingleton<SocketService>(() => SocketService());

And this how I use it in my app:

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final SocketService _socketService = sl<SocketService>();

  @override
  void dispose() {
    _socketService.dispose();
    _client.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      ...
          );
        },
      ),
    );
  }
}

Am I doing something wrong?

Elevencode avatar Jul 17 '23 17:07 Elevencode

FYI: not sure whether it's related to this or not - https://github.com/rikulo/socket.io-client-dart#memory-leak-issues-in-ios-when-closing-socket

Please use socket.dispose() instead of socket.close() or socket.disconnect() to solve the memory leak issue on iOS.

jumperchen avatar Jul 18 '23 10:07 jumperchen

@jumperchen None of these solutions work. I already have the autoConnect: false flag set. The dispose() method doesn't work either.

Note: my app works on the web.

Elevencode avatar Jul 18 '23 10:07 Elevencode