I have a Flutter app that connects to my Node.js server via socket.io. It’s working great. However, I want to display an error message if my server drops (not the client, the app).
When I tried to pass the error to the stream controller, Flutter throws an error (obviously). Because the returned data from the error is not of the same type of the working model response from the server.
I tried to add another stream controller, but that means there will be 2 different widgets in the ‘body’, and I don’t how to distinguish between the two at the ‘body’ level. As the snapshot is not yet implemented yet!
You will find below:
_socket.onConnectError
and
_socket.onDisconnect
These are responsible for passing the error, or printing them in the console in this case.
I want to pass these to a text widget in the home_screen.dart.
My question is, how can I display the error from socket.io as a text widget for the user to see?
Here’s my code for reference:
active_layout.model.dart:
import 'dart:convert';
/*
To parse this JSON data, do
final activeLayout = activeLayoutFromJson(jsonString);
*/
ActiveLayout activeLayoutFromJson(String str) => ActiveLayout.fromJson(json.decode(str));
String activeLayoutToJson(ActiveLayout data) => json.encode(data.toJson());
class ActiveLayout {
final int status;
final Playlist playlist;
ActiveLayout({
required this.status,
required this.playlist
});
factory ActiveLayout.fromJson(Map<String, dynamic> json) => ActiveLayout(
status: json["status"],
playlist: Playlist.fromJson(json["playlist"])
);
Map<String, dynamic> toJson() => {
"status": status,
"playlist": playlist.toJson()
};
}
class Playlist {
final String id;
final String name;
Playlist({
required this.id,
required this.name
});
factory Playlist.fromJson(Map<String, dynamic> json) => Playlist(
id: json["_id"],
name: json["name"]
);
Map<String, dynamic> toJson() => {
"_id": id,
"name": name
};
}
active_layout_stream.dart:
import 'dart:async';
import 'package:socket_io_client/socket_io_client.dart' as io;
import '../constants.dart';
import '../models/active_layout.model.dart';
class ActiveLayoutStream {
late io.Socket _socket;
final StreamController<ActiveLayout> _activeLayoutStreamController = StreamController();
Stream<ActiveLayout> get getResponse => _activeLayoutStreamController.stream;
void _connectSocket(String uuid) {
_socket.onConnect((data) => print('Connection established'));
_socket.onConnectError((data) => print("error: $data"));
_socket.onDisconnect((data) => print("disconnect: $data"));
_socket.on('playlist', (data) {
_activeLayoutStreamController.add(activeLayoutFromJson(data));
});
_socket.emit('join', { "uuid": uuid });
}
void _initializeSocketConnection() async {
try {
_socket = io.io(
socketUri,
io.OptionBuilder()
.setTransports(['websocket'])
.setQuery({ "uuid": uuid })
.build()
);
_connectSocket(uuid);
} catch(e) {
throw Exception('error caught: $e');
}
}
ActiveLayoutStream() {
_initializeSocketConnection();
}
void dispose(){
_socket.disconnect();
_socket.dispose();
_activeLayoutStreamController.close();
}
}
home_screen.dart:
import 'package:flutter/material.dart';
import '../models/active_layout.model.dart';
import '../utils/active_layout_stream.dart';
import '../widgets/text_and_layout_stack_widget.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final ActiveLayoutStream _activeLayoutStream = ActiveLayoutStream();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: StreamBuilder<ActiveLayout>(
stream: _activeLayoutStream.getResponse,
builder: (context, AsyncSnapshot<ActiveLayout> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text("No connection");
case ConnectionState.waiting:
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text("Loading...")
]
)
);
case ConnectionState.active:
ActiveLayout layoutDetails = snapshot.data as ActiveLayout;
if(layoutDetails.status == 404) {
return Center(
child: Text(
"No playlist available yet",
style: Theme.of(context).textTheme.displaySmall!
)
);
} else {
return TextAndLayoutStackWidget(layoutDetails: layoutDetails);
}
case ConnectionState.done:
return const Text("Connection terminated");
default:
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text("Loading...")
]
)
);
}
}
)
)
);
}
}