0

I've been frustrated with this error with the getstream api for a while now, especially because it is erratic. I don't know exactly how to reproduce it, which makes it tough to debug. My only "solution" to it is to log out and log back in (this forces the user to reconnect to the get stream api server), thereby, getting the token again. But obviously, this shouldn't have to be the case.

I get this error whenever I open a message between two people.

Here's the main.dart where I initialize the stream client

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  final client = StreamChatClient(
      STREAM_API_KEY,
    logLevel: Level.OFF,
  );
  runApp(MyApp(
    client: client,
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key, required this.client}) : super(key: key);

  final StreamChatClient client;

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {

    return Provider<BaseAuth>(
      create: (context) => Auth(), //provide an instance of our Auth Service to the entire app
      child: MaterialApp(
        title: 'App',
        theme: ThemeData(
          primarySwatch: Colors.indigo,
        ),
        home: const SplashscreenWidget(),
        debugShowCheckedModeBanner: false,
        builder: (context, widget) => StreamChat( //was StreamChatCore
          client: client,
          child: widget!,
        ),
      ),
    );
  }

Here in my main page, I connect the user to the get stream api. I have left out the parts of the code that are not pertinent to this question. It is impossible for a user to get to messages without first getting to this home page. So it should not be possible for the user to have an unauthorized token when they open a message:

    void initState() {
        super.initState();
        
        //only connect user if coming from signup or login page
        if(widget.fromSignUpOrLogin) {
          connectUser = _connectUser();
        }
      }


_connectUser() async {
    final client = StreamChatCore.of(context).client;

    FireStoreDatabase db = FireStoreDatabase(uid: FirebaseAuth.instance.currentUser!.uid);
    AppUser appUser = await db.readUser();

    final user = su.User(id: uid,
        extraData: {
      "name": appUser.username,
      "image": appUser.photoUrl,
        }
    );

    await client.connectUser(
        user,
        appUser.streamToken
    );
  }

Lastly, here's my page of a message between two people. Again, I have left out the code that's not the scope of this question:

     late Channel globalChannel;
        
          initMessageSystem() async {
            globalChannel = widget.client.channel('messaging', id: widget.channelId);
            await globalChannel.watch();
          }
    
    @override
      void initState() {
        super.initState();
        initMessageSystem();
      }

@override
  Widget build(BuildContext context) {

    return StreamChat( ///this must be here as the root of the message chat
      client: widget.client,
      child: StreamChannel
        (
        child: _buildBody(),
        channel: globalChannel,
      ),
      streamChatThemeData: StreamChatThemeData(
        colorTheme: StreamColorTheme.light(
          accentPrimary: Colors.purpleAccent,
            textHighEmphasis: Colors.purpleAccent,
        ),
        ownMessageTheme: const StreamMessageThemeData(
          messageBackgroundColor: Colors.purpleAccent,
          messageTextStyle: TextStyle(
            color: Colors.white
          )
        )
      )
    );
  }

Because of how erratic the problem is, I believe a possible solution is to check whether or not a client is connected first in the initMessage() function and then connect the client if they are not and do nothing if they are. However, the get stream api is not to clear on how to check if a client is connected using dart. So I was wondering how to check for this? or better still, if there is a better solution for what I am experiencing.

2 Answers2

0

I believe this is happening because you're not waiting for the client.connectUser to complete before navigating to the messages screen (and initiating a call to queryChannel with the Stream API).

I'm making this assumption because I also noted that you're not correctly awaiting the globalChannel. In initState you're calling initMessageSystem, but the build method may be called before that future completes.

You can use a FutureBuilder to await the results of any futures within the build method. Not properly awaiting channel.watch won't cause this error you're seeing, but client.connectUser not completing will.

Take a look at the getting started tutorial for Stream Chat Flutter if you haven't: https://getstream.io/chat/flutter/tutorial/

There is also a StreamChannelListView that will handle the complexity of getting all channels, and navigating to a channel page.

Some other notes:

  • You don't need to create StreamChat again in your message page, as you already created it in MaterialApp builder, which will expose it to every route in your Flutter app.
Gordon Hayes
  • 231
  • 1
  • 7
  • thanks for the response! but sorry I am kind of confused. In my home page, I await the connectUser() in my _connectuser() method that I have posted above. And the body of that page is wrapped in a future builder, so it is indeed impossible to get to messages without being connected. Secondly, could you please say how to correctly wait for the globalChannel? – youcantgetridofmestackoverflow Jul 08 '22 at 15:09
  • Do you mind showing that code, or to share a gist/repo with the full sample. It'll help me determine the issue – Gordon Hayes Jul 11 '22 at 08:57
0

I feel your pain. For some reason, your client is disconnecting (for reasons i am yet to figure out) so you want to patch this by reconnecting the user every time they go into the message page. But get stream api would throw you an error saying a connection for your client already exists. As a result, it would make sense to check for the connection state of the client in your init and if disconnected, connect, else, do nothing.

Here should be your initMessage:

//This future will be assigned to the initMessageSystem() function
  Future? initMessage;        
initMessageSystem() async {
        
        if(widget.client.wsConnectionStatus == ConnectionStatus.disconnected) {
          await _connectUser(); //we have to wait for the reconnection to complete before page is built
        }
        globalChannel = widget.client.channel('messaging', id: widget.channelId);
    
      
        await globalChannel.watch();
      }

Here should be your init:

@override
  void initState() {
    super.initState();
    //we assign the function to the future variable in the init method to avoid multiple runs
    initMessage = initMessageSystem();
  }

Then wrap your body in a future builder and make the initmessage function the future. This way, the client connects before the page builds:

@override
  Widget build(BuildContext context) {

    //lastly, we wrap the body in a future builder to ensure
    // the initMessage() async function in the init() runs first
    return FutureBuilder<dynamic>(
      future: initMessage, // async work
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.waiting: return const Center(child: CircularProgressIndicator());
          default:
            if (snapshot.hasError) {
              return Center(child: Text('Error: ${snapshot.error}'));
            } else {
              return StreamChat( ///this must be here as the root of the message chat
                  client: widget.client,
                  child: StreamChannel
                    (
                    child: _buildBody(),
                    channel: globalChannel,
                  ),
                  streamChatThemeData: StreamChatThemeData(
                      colorTheme: StreamColorTheme.light(
                        accentPrimary: Colors.purpleAccent,
                        textHighEmphasis: Colors.purpleAccent,
                      ),
                      ownMessageTheme: const StreamMessageThemeData(
                          messageBackgroundColor: Colors.purpleAccent,
                          messageTextStyle: TextStyle(
                              color: Colors.white
                          )
                      )
                  )
              );
            }
        }
      },
    );
  }
Duck Dodgers
  • 223
  • 3
  • 14