kerimamansaryyev / dart_pusher_channels

MIT License
48 stars 13 forks source link

Error using with state management #47

Closed nicolasvahidzein closed 1 month ago

nicolasvahidzein commented 7 months ago

Hello. Thank you so much for a wonderful package, it was a lifesaver after many months of searching.

I have a question about using your package with MobX state management. It is telling me that PusherChannelsClient is an invalid type

Undefined class 'InvalidType'. Try changing the name to the name of an existing class, or creating a class with the name 'InvalidType'.dartundefined_class

It was working before i believe but it might have changed after an update perhaps.

Any pointers?

I am using websockets from many different parts of my app and connecting to different channels. Thanks.

kerimamansaryyev commented 7 months ago

Hello @nicolasvahidzein !

Please, provide version of dart_pusher_channels, a portion of a code and a full log of an error

kerimamansaryyev commented 7 months ago

I believe the issue is not related to the package since all the classes provided (including PusherChannelsClient) are pure Dart classes, no annotations, no dependency injection.

I believe you are trying to run some codegen upon mobx which uses build_runner as a transitive dependency. Anyways, I need some piece of a code along with the pubspec to help you on that.

nicolasvahidzein commented 7 months ago

Hello, Kerim, I am using v 1.2.1. Not sure how i can send you the code the app is massive. I always have this problem on GitHub and for the life of me i never know how to share code.

kerimamansaryyev commented 7 months ago

You have just sent me an error without a context. I don't even know whether it's a static error or runtime error. I need way more things to help you. Logs, screenshots of a code, a piece of a code, logs and e.t.c. So share some insights about the error, please in the ways I described. Try to look up markdown tips to paste a code. For example, wrap the code inside markdown code blocks.

kerimamansaryyev commented 7 months ago

@nicolasvahidzein Refer to this for markdown: https://www.freecodecamp.org/news/markdown-cheat-sheet/

nicolasvahidzein commented 7 months ago

I will read this article today Kerim @kerimamansaryyev and revert back. I think more than anything that my code is not well structured. I was trying to recycle the pusher connection between channel subscription but it seems wrong.

Do you have an example of the proper way to connect to different channel on the same server from within different parts of your app?

kerimamansaryyev commented 7 months ago

I will read this article today Kerim @kerimamansaryyev and revert back. I think more than anything that my code is not well structured. I was trying to recycle the pusher connection between channel subscription but it seems wrong.

Do you have an example of the proper way to connect to different channel on the same server from within different parts of your app?

Sure, check this project: https://github.com/kerimamansaryyev/pusher_channels_test_app

It’s implemented on Feature Driven Design with detailed docs. Here is the brief general idea: you must make your client as a singleton so you will have 1 instance of your client and later you can create instances of channels upon it, don’t worry about channel instances, their state is managed by client internally so their states shall stay up-to-date along with the event bindings - event binding streams won’t be interrupted even on client’s connection life cycle change (unless client was disposed)

kerimamansaryyev commented 7 months ago

So it’s a general and brief idea. You now have the reference and idea, of course, you may need adjust to your own case along with testing. Feel free to reach me, I will be monitoring the issue

nicolasvahidzein commented 7 months ago

That was my mistake. Not a singleton. Thank you so much.

kerimamansaryyev commented 7 months ago

@nicolasvahidzein Should I close the issue?

nicolasvahidzein commented 7 months ago

Hey Kerim. No please. Give me another day. Im rewriting everything and will share my code.

nicolasvahidzein commented 7 months ago

Here is my singleton, i'm quite happy. Did i miss something you think?

//flutter packages
import 'dart:async';
import 'package:dart_pusher_channels/dart_pusher_channels.dart';

//project packages
import 'package:zandu_mobile_admin/constants/main.dart';
import 'package:zandu_mobile_admin/services/user_repository.dart';

class WebsocketsSingleton {
    //This creates the single instance by calling the `_internal` constructor specified below
    static final WebsocketsSingleton _singleton = WebsocketsSingleton._internal();
    WebsocketsSingleton._internal();

    //This is what's used to retrieve the instance through the app
    static WebsocketsSingleton getInstance() => _singleton;

    //variables
    final String _fileName = 'websockets_singleton.dart';
    final _userRepository = UserRepository();
    List _channelList = [];//the array that will hold all our lists
    late PusherChannelsClient? pusher;
    bool alreadyRun = false;

    //first thing we need to do when we launch the app and log in successfully
    Future<void> initialize() async {
        /* trace */ if (showFunctionPrintStatements) { print('initialize function inside $_fileName'); }

        if (alreadyRun == false) {
            //never run yet

            alreadyRun = true;

        } else {
            //already ran, do nothing

            //the only reason we will allow this function to run again is if pusher is null
            if (pusher == null) {
                //pusher is null, we have a problem

            } else {
                //pusher is not null, we are good to go

                //abort
                return;

            }

        }

        //create the base variables
        //String websocketsAppKey = '';//never used
        String websocketsAppId = '';
        String websocketsDomain = '';
        int websocketsPort = 0;
        //String websocketsCluster = '';//never used
        String websocketsScheme = 'ws';

        //check which mode we are in
        if (appMode == 'dev') {
            //we are in "dev" mode

            //websocketsAppKey = websocketsAppKeyDev;
            websocketsAppId = websocketsAppIdDev;
            websocketsDomain = websocketsDomainDev;
            websocketsPort = websocketsPortDev;
            //websocketsCluster = websocketsClusterDev;

            if (websocketsEncryptedDev == true) {
                websocketsScheme = 'wss';
            }

        } else {
            //we are in "prod" mode

            //websocketsAppKey = websocketsAppKeyProd;
            websocketsAppId = websocketsAppIdProd;
            websocketsDomain = websocketsDomainProd;
            websocketsPort = websocketsPortProd;
            //websocketsCluster = websocketsClusterProd;

            if (websocketsEncryptedProd == true) {
                websocketsScheme = 'wss';
            }

        }

        //THIS WILL BE RE ADDED AS A FEATURE IN THE COMING VERSIONS
        //PusherChannelsPackageConfigs.enableLogs();
        //PusherChannelsPackageConfigs.disableLogs();

        PusherChannelsOptions options = PusherChannelsOptions.fromHost(
            scheme: websocketsScheme,
            key: websocketsAppId,
            host: websocketsDomain,
            port: websocketsPort,
        );

        pusher = PusherChannelsClient.websocket(
            minimumReconnectDelayDuration: const Duration(seconds: 3),
            options: options,
            connectionErrorHandler: (error, trace, refresh) {
                print('websocket error: $error');
            },
        );

        //Ensure you implement your logic after successfull connection to your server
        //Channel? channel;//TURNED INTO A CLASS VARIABLE

        //This stream will receive events and notify subscribers whenever the client is connected or reconnected after potential error
        //StreamSubscription? eventSubscription;//TURNED INTO A CLASS VARIABLE

        pusher!.onConnectionEstablished.listen((_) async {

        });

        print('will attempt to connect the pusher service');

        //connect the service
        pusher!.connect();

        print('pusher service connection attempt done');

    }

    void dispose() {

        for (var i = 0; i < _channelList.length; i++) {

            StreamSubscription? eventSubscription = _channelList[i]['event'];

            eventSubscription?.cancel();

        }

        //now disconnect from everywhere
        disconnectFromAllChannels();

    }

    Future<void> connectToChannel(
        String channelName,
        String eventName,
        Function(dynamic data) eventTriggered,
    ) async {

        //create the final channel name because in some instances it is a concatenated value and needs to be assembled
        String? defaultChannelName;

        //determine which channel if any we will be calling
        switch (channelName) {

            case 'test_channel':

                defaultChannelName = 'private-user.mike@hola.com';

                break;

            default:
                //do nothing

        }

        if (pusher == null) {
            //pusher is null, we have a problem

            await initialize();

            if (pusher == null) {
                //stop on second attempt
            }

        } else {
            //pusher is not null, we are good to go
        }

        //check if the defaultChannelName variable is null or not 
        if (defaultChannelName == null) {
            //not good, it's null

            //abort
            return;

        } else {
            //it's not null, we can keep going

            //make sure the channel is not already created
            for (var i = 0; i < _channelList.length; i++) {

                if (_channelList[i]['channel'] == channelName) {
                    //found an identical channel, abort

                    //abort
                    return;

                }

            }

            Map tokenData = await _userRepository.retrieveToken();
            String accessToken = tokenData['accessToken'];

            //create the base variables
            String websocketsAuthEndpointDomain = '';
            String websocketsAuthEndpointRelativeUrl = '';

            //check which mode we are in
            if (appMode == 'dev') {
                //we are in "dev" mode

                websocketsAuthEndpointDomain = websocketsAuthEndpointDomainDev;
                websocketsAuthEndpointRelativeUrl = websocketsAuthEndpointRelativeUrlDev;

            } else {
                //we are in "prod" mode

                websocketsAuthEndpointDomain = websocketsAuthEndpointDomainProd;
                websocketsAuthEndpointRelativeUrl = websocketsAuthEndpointRelativeUrlProd;

            }

            //PUBLIC CHANNEL EXAMPLE
            //channel ??= pusher.publicChannel('my_public_channel_name');

            //PRIVATE CHANNEL EXAMPLE
            //connect to a private channel
            Channel channel = pusher!.privateChannel(
                defaultChannelName,
                authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
                    authorizationEndpoint: Uri.parse(websocketsAuthEndpointDomain + websocketsAuthEndpointRelativeUrl),
                    headers: {
                        'Authorization':  'Bearer ' + accessToken,
                        //'Content-Type': 'application/json',//supposedly this header was causing trouble but even after removing it I could not access the private-user.useremail channel
                        'Accept': 'application/json'
                    },
                ),
            );

            //await _eventSubscription?.cancel();

            // Ensure you bind to the channel before subscribing, otherwise - you will miss some events
            StreamSubscription eventSubscription = channel.bind(eventName).listen((event) {
                //listen for events form the channel here

                eventTriggered('hola');

            });

            channel.subscribe();

            Map newChannel = {
                'channel': channel,
                'event': eventSubscription,
            };

            _channelList.add(newChannel);

        }

    }

    //you can only disconnect from a channel that this class listened to, meaning you can call this from the same class that triggered the listening event on that partical channel.
    Future<void> disconnectFromChannel(String channelName) async {
        /* trace */ if (showFunctionPrintStatements) { print('disconnectFromChannel function inside $_fileName'); }

        for (var i = 0; i < _channelList.length; i++) {

            if (_channelList[i]['channel'] == channelName) {
                //found the correct channel

                Channel? channel = _channelList[i]['channel'];

                channel?.unsubscribe();

                _channelList.removeAt(i);

            }

        }

    }

    Future<void> disconnectFromAllChannels() async {
        /* trace */ if (showFunctionPrintStatements) { print('disconnectFromAllChannels function inside $_fileName'); }

        for (var i = 0; i < _channelList.length; i++) {

            Channel? channel = _channelList[i]['channel'];

            channel?.unsubscribe();

        }

        _channelList = [];

    }

}
nicolasvahidzein commented 7 months ago

And this is how i import it:

`` //singletons import 'package:zandu_mobile_admin/singletons/websockets_singleton.dart';

final _websockets = WebsocketsSingleton.getInstance();

    await _websockets.connectToChannel(
        'test_channel',
        'App\\Events\\UserStatusChange',
        (dynamic data){
            //data was received

            print('event was received');
            print(data);

        }
    );

``

kerimamansaryyev commented 1 month ago

@nicolasvahidzein how did it go? Should I close the issue?

nicolasvahidzein commented 1 month ago

Yes i believe so its been a while.