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.
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
)
)
)
);
}
}
},
);
}