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

467

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.

Share:
467
Amine Da.
Author by

Amine Da.

Intern at Microsoft. Working with : WPF, Universal apps 10 and .net .

Updated on January 04, 2023

Comments

  • Amine Da.
    Amine Da. over 1 year

    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

  • Amine Da.
    Amine Da. about 2 years
    makes sense , thank you. I'll try to make a factory constructor.
  • Amine Da.
    Amine Da. about 2 years
    I'm getting this error though, A value of type 'Socket' can't be returned from the constructor 'SocketService' because it has a return type of 'SocketService'
  • Amine Da.
    Amine Da. about 2 years
    should i put the socket connection inside the internal constructor ? I'm not sure where should i connect to the socket. i'll update my question with some code
  • Devarsh Ranpara
    Devarsh Ranpara about 2 years
    Hi, I have checked out your implementation, and seems like there is not any issues in your factory constructor. Althogh you should create one method for initialize the socket and you should not put it inside the internal constructor. Let me know if you still face any issues.
  • Amine Da.
    Amine Da. about 2 years
    Should the internal constructor stay empty? is it just made for caching the instance? What's the point of the internal constructor if i shouldn't initialize the socket in it?
  • Devarsh Ranpara
    Devarsh Ranpara about 2 years
    Internal constructor can have code, but it should not have expensive calls. Creating a new function for init would be best practice for this kind of scenarios.
  • Amine Da.
    Amine Da. about 2 years
    I did create a function for init , but that made creating more than one socket instance possible. should the function init be private and static? but that means i need to call it inside the constructor
  • Devarsh Ranpara
    Devarsh Ranpara about 2 years
    Does creating init function solved your issue? You need to use constructor only for providing instance of class and use init method for socket connection, you can check in init method, is connection is already there you can avoid execution of whole Soekct.
  • Amine Da.
    Amine Da. about 2 years
    socket init function can be called and connects multiple times even if the socket is static and checking if it's already connected. I updated the code and output. PLZ take a look and if you notice an error, can you update your answer with some code? thanks in advance
  • Devarsh Ranpara
    Devarsh Ranpara almost 2 years
    Okay I will try to run this code and get back to you, can you tell me which plugin you are using with version. Your code does not look nullsafe, so Flutter version will be appriciated as well.
  • Amine Da.
    Amine Da. almost 2 years
    my code is null safe I'm using the latest dart and flutter version, I updated my post with yaml file