Search code examples
flutterdartsocket.iobloc

use socket.io client in dart in different places with a single instance


I use Bloc and repositories, I'm going to use the socket in different places in the project. each api package has its own events but shares the same socket. I want to connect once and use it in multiple places and packages. Is that possible?

// use socket in multiple files 
final socket = SocketApi().init();

In javascript I would export { socket }

package : https://pub.dev/packages/socket_io_client


UPDATE: socket init function can be called and connects multiple times even if the socket is static and checking if it's already connected

import 'dart:async';  
import 'package:ngi_api/ngi_api.export.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;

class StreamSocketController<T> {
  StreamSocketController() {
    print('Init Stream controller ${T.toString()}');
  }
  final _socketResponse = StreamController<T>();

  void Function(T) get addResponse => _socketResponse.sink.add;

  Stream<T> get getResponse => _socketResponse.stream;

  void dispose() {
    _socketResponse.close();
  }
}

class SocketApi {
  // Factry constructor to retutn same static instance everytime you create any object.
  factory SocketApi() {
    return _socketApi;
  }

  // An internal private constructor to access it for only once for static instance of class.
  SocketApi._internal();

  static void init() {
    print('socket init connected: ${socket.connected}');
    if (socket.connected == false) { 
      socket.connect();
      socket.on('server:connected', (dynamic data) {
        print(
          'socket instance created and connected',
        );
        socket.emit(
          'user:connected',
          <dynamic, dynamic>{'user': Environment.user},
        );
      });
      socket.on('unauthorized', (dynamic data) {
        print('Unauthorized');
      });
      socket.onError(
        (dynamic error) => {print(error), print('socket error')},
      );
      socket.onDisconnect((dynamic data) {
        print('socket instance disconnected');
      });
    } else {
      print('socket instance already connected');
    }
  }

  // A static private instance to access _socketApi from inside class only
  static final SocketApi _socketApi = SocketApi._internal();

  static IO.Socket socket = IO.io(
    TcFleetTunEnvironment.socketURL, 
    IO.OptionBuilder()
        .setTransports(['websocket'])
        .disableAutoConnect()
        .enableForceNewConnection()
        .setTimeout(5000)
        .setReconnectionDelay(10000)
        .enableReconnection()
        .setQuery(
          <dynamic, dynamic>{'token': Environment.token},
        )
        .build(),
  );

  // All socket related functions.
  static Stream<Asset> getAsset() async* {
    final streamSocket = StreamSocketController<Asset>();
    try {
      socket.on('newMsg', (dynamic data) {
        try {
          streamSocket
              .addResponse(Asset.fromJson(data as Map<String, dynamic>));
        } catch (e, stackTrace) {
          print('Exception newMsg');
          print(e);
          print(stackTrace);
          print(data);
        }
      });
      yield* streamSocket.getResponse.take(20);
    } catch (e) {
      print('Exception getAsset');
      print(e);
    } finally {
      print('Stream controller asset closed');
      socket.off('newMsg');
      streamSocket.dispose();
    }
  }
}

//main
void main() async {
  SocketApi.init();
  SocketApi.init();
  SocketApi.getAsset().listen(
    (Asset data) {
      print('Asset.1: ${data.name}');
    },
    cancelOnError: false,
    onError: print,
    onDone: () {
      print('*** asset.1 stream controller Done ***');
    },
  );
}

this is the result if i call socketApi.init() multiple times, socket already connected never gets printed , instead a new connection is created

socket init multiple connection

This is my dependencies file

enter image description here


Solution

  • To achieve your required functionality you will need to create one class for Socket IO plugin which will do all the interections with SocketIO package's code and also you will need to create one factory constructor so use same SocketApi object across the application.

    Example of factory constructor.

    class SocketApi {
            // A static private instance to access _socketApi from inside class only
            static final SocketApi _socketApi = SocketApi._internal();
    
            // An internal private constructor to access it for only once for static instance of class.
            SocketApi._internal();
    
            // Factry constructor to retutn same static instance everytime you create any object.        
            factory SocketApi() {
                return _socketApi;
            }
    
            // All socket related functions.
        
        }
    

    Usage

    SocketApi socketApi = SocketApi();
    

    You can learn more about factory constructor here.