I'm trying to create a websocket connection in Flutter.
I followed this tutorial and this
My query is that how can we know once the websocket connection is established? Like there is onOpen event in Javascript.
My code in flutter is:
void main() async {
final channel = IOWebSocketChannel.connect('wss://my.server.org');
channel.stream.listen(
(dynamic message) {
debugPrint('message $message');
},
onDone: () {
debugPrint('ws channel closed');
},
onError: (error) {
debugPrint('ws error $error');
},
);
}
This is what I used in one project to connect to AWS AppSync service. The communication is implemented over websockets.
What you do is parse the messages you receive through web socket. Messages will carry the data, but also there are messages to tell you that the connection was established, keep alive message etc.
I don't remember if the messages below are AppSync specific, or are applicable to any websocket server - but it should at least give you an idea how this could work.
var channel=IOWebSocketChannel.connect(url, protocols: ['graphql-ws']);
channel.sink.add(json.encode({"type": "connection_init"}));
channel.stream.listen((event) {
var e = json.decode(event);
switch (e['type']) {
case 'connection_ack':
wsTimeoutInterval = e['payload']['connectionTimeoutMs'];
var register = {
'id': uniqueKey,
'payload': {
'data': json.encode(query),
'extensions': {'authorization': header}
},
'type': 'start'
};
var payload = json.encode(register);
channel.sink.add(payload);
break;
case 'data':
// calling a callback function Function(Map<String, dynamic>) listener
listener(e);
break;
case 'ka':
print('Keep alive message received!!!');
break;
case 'start_ack':
print('Ws Channel: Subscription started');
break;
default:
print('Unimplemented event received $event');
}
}, onError: (error, StackTrace stackTrace) {
// error handling
print('Ws Channel: $error');
}, onDone: () {
// communication has been closed
channel.sink.close();
print('Ws Channel: Done!');
});
Related
The WebSocket server I use needs to receive a message after the connection.
The only way seems to use WebSocket.connect instead of WebSocketChannel.connect (https://github.com/dart-lang/web_socket_channel/issues/209):
import 'package:web_socket_channel/io.dart';
// ...
IOWebSocketChannel? _channel;
// ...
WebSocket.connect("ws://...").then((ws) {
this._channel = IOWebSocketChannel(ws);
_channel!.sink.add(msg);
}
But this is not supported by web. How is it possible to do that with both mobile and web support ?
WebSocket.connect(socketUrl).then((value) {
_channel = IOWebSocketChannel(value);
_channel?.stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: true);
debugPrint(
"webSocket——readyState:${_channel?.innerWebSocket?.readyState}");
if (_channel?.innerWebSocket?.readyState == 1) {
} else {}
}).timeout(const Duration(seconds: 10));
I am trying to create a mobile app that can connect to mobile wallets (ex: Metamask, TrustWalet,...) via WalletConnect, but i can't find anything.
Is there any way to implement walletconnect on flutter app yet?
You need walletconnect_dart and url_launcher
import 'package:url_launcher/url_launcher_string.dart';
import 'package:walletconnect_dart/walletconnect_dart.dart';
// Create a connector
final connector = WalletConnect(
bridge: 'https://bridge.walletconnect.org',
clientMeta: PeerMeta(
name: 'WalletConnect',
description: 'WalletConnect Developer App',
url: 'https://walletconnect.org',
icons: [
'https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
],
),
);
// Subscribe to events
connector.on('connect', (session) => print(session));
connector.on('session_update', (payload) => print(payload));
connector.on('disconnect', (session) => print(session));
// Create a new session
if (!connector.connected) {
final session = await connector.createSession(
onDisplayUri: (uri) async {
_uri = uri;
await launchUrlString(uri, mode: LaunchMode.externalApplication);
}
);
}
For more information please visit walletconnect_dart
dependencies:
wallet_connect: ^1.0.2
final wcClient = WCClient(
onConnect: () {
// Respond to connect callback
},
onDisconnect: (code, reason) {
// Respond to disconnect callback
},
onFailure: (error) {
// Respond to connection failure callback
},
onSessionRequest: (id, peerMeta) {
// Respond to connection request callback
},
onEthSign: (id, message) {
// Respond to personal_sign or eth_sign or eth_signTypedData request callback
},
onEthSendTransaction: (id, tx) {
// Respond to eth_sendTransaction request callback
},
onEthSignTransaction: (id, tx) {
// Respond to eth_signTransaction request callback
},
);
For More Check : Link
I'm implementing socket.
Two clients connect to the server with no problem, when client1 sends a message to the server, the server publishes it to every other client (which in this case is client2). but client2 won't get the message unless it sends a message. It seems the listener of the client doesn't work. Obviously, I want client2 to get the message from client1 instantly.
here is my sever code:
import 'dart:io';
import 'dart:typed_data';
void main() async {
// bind the socket server to an address and port
MySocket mySocket = MySocket();
await mySocket.init();
}
class MySocket {
ServerSocket? server;
List<Socket> clients = [];
//initialize the socket
init() async {
server = await ServerSocket.bind("192.168.0.112", 4000);
// listen for client connections to the server
server!.listen((client) {
handleConnection(client);
addClient(client);
});
}
void handleConnection(Socket client) async {
print('Connection from'
' ${client.remoteAddress.address}:${client.remotePort}');
// listen for events from the client
client.listen(
// handle data from the client
(Uint8List data) async {
await Future.delayed(Duration(seconds: 1));
final message = String.fromCharCodes(data);
print(message);
publish(message, client);
},
// handle errors
onError: (error) {
print(error);
client.close();
},
// handle the client closing the connection
onDone: () {
print('Client left');
client.close();
},
);
}
void addClient(Socket client) {
//if client doesn't already exist add it to the list of clients
if (!clients.any((element) =>
'${client.remoteAddress.address}:${client.remotePort}' ==
'${element.remoteAddress.address}:${element.remotePort}')) {
clients.add(client);
}
}
void publish(String message, Socket client) {
//write the message to every client except the author of it
clients.forEach((element) async {
if ('${client.remoteAddress.address}:${client.remotePort}' !=
'${element.remoteAddress.address}:${element.remotePort}') {
element.write(message);
}
});
}
}
here is my client-side code:
import 'dart:io';
import 'dart:typed_data';
void main() async {
//gets the username
String name = '';
while (name.isEmpty) {
print('Enter your name: ');
name = stdin.readLineSync() ?? '';
}
// connect to the socket server
final socket = await Socket.connect("192.168.0.112", 4000);
print('Connected to: ${socket.remoteAddress.address}:${socket.remotePort}');
// listen for responses from the server
socket.listen(
// handle data from the server
(Uint8List data) {
final serverResponse = String.fromCharCodes(data);
print('$serverResponse');
},
// handle errors
onError: (error) {
print(error);
socket.destroy();
},
// handle server ending connection
onDone: () {
print('Left server.');
socket.destroy();
},
);
// sending messages to the server
String message = "";
while (message != "exit") {
message = stdin.readLineSync() ?? '';
await sendMessage(socket, name, message);
}
socket.close();
}
Future<void> sendMessage(Socket socket, String name, String message) async {
socket.write('$name: $message');
await Future.delayed(Duration(seconds: 2));
}
Thank you in advance.
The issue is because stdin.readLineSync() in your client blocks the current thread. You can get around this by spawning an isolate to handle that portion of the code, so that it does not block the socket.listen from printing out the responses from the server.
See updated client code below:
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
void main() async {
//gets the username
String name = '';
while (name.isEmpty) {
print('Enter your name: ');
name = stdin.readLineSync() ?? '';
}
// connect to the socket server
final socket = await Socket.connect("192.168.0.112", 4000);
print('Connected to: ${socket.remoteAddress.address}:${socket.remotePort}');
// listen for responses from the server
socket.listen(
// handle data from the server
(Uint8List data) {
final serverResponse = String.fromCharCodes(data);
print('$serverResponse');
},
// handle errors
onError: (dynamic error) {
print(error);
socket.destroy();
},
// handle server ending connection
onDone: () {
print('Left server.');
socket.destroy();
},
);
final receive = ReceivePort();
final isolate = await Isolate.spawn(readMessages, receive.sendPort);
await for (final message in receive) {
if (message == 'exit') break;
await sendMessage(socket, name, message as String);
}
socket.close();
}
void readMessages(SendPort port) {
String message = '';
while (message != 'exit') {
message = stdin.readLineSync() ?? '';
port.send(message);
}
Isolate.exit(port);
}
Future<void> sendMessage(Socket socket, String name, String message) async {
socket.write('$name: $message');
await Future<void>.delayed(Duration(seconds: 2));
}
Objective is simple
flutter app makes a call to graphql api over websockets
app view calls the controller, controller calls the provider, provider calls the AWS appsync api over websockets or over HTTP api socket call
we receive a stream of data from appsync api or HTTP api socket call over websockets every now and then from backend
streams need to be cascaded back to provider , and then to controller (this is the critical step)
controller (not the provider) would update the obs or reactive variable, make the UI reflect the changes
problem : data is recieved via websockets in the caller, but never passed back as stream to provider or controller to reflect the changes
sample code
actual caller
orderdata.dart
#override
Stream<dynamic> subscribe({
String query,
Map<String, dynamic> variables,
}) async* {
debugPrint('===->subscribe===');
// it can be any stream here, http or file or image or media
final Stream<GraphQLResponse<String>> operation = Amplify.API.subscribe(
GraphQLRequest<String>(
document: query,
variables: variables,
),
onEstablished: () {
debugPrint(
'===->subscribe onEstablished ===',
);
},
);
operation.listen(
(event) async* {
final jsonData = json.decode(event.data.toString());
debugPrint('===->subscription data $jsonData');
yield jsonData;
},
onError: (Object e) => debugPrint('Error in subscription stream: $e'),
);
}
in the provider
orderprovider.dart
Stream<Order> orderSubscription(String placeId) async* {
debugPrint('===->=== $placeId');
subscriptionResponseStream = orderData.subscribe(
query: subscribeToMenuOrder,
variables: {"place_id": placeId},
);
subscriptionResponseStream.listen((event) async* {
debugPrint(
"===->=== yielded $event",
);
yield event;
});
debugPrint('===->=== finished');
}
in the controller
homecontroller.dart
Future<void> getSubscriptionData(String placeId) async {
debugPrint('===HomeController->getSubscriptionData===');
OrderProvider().orderSubscription(placeId).listen(
(data) {
//this block is executed when data event is receivedby listener
debugPrint('Data: $data');
Get.snackbar('orderSubscription', data.toString());
},
onError: (err) {
//this block is executed when error event is received by listener
debugPrint('Error: $err');
},
cancelOnError:
false, //this decides if subscription is cancelled on error or not
onDone: () {
//this block is executed when done event is received by listener
debugPrint('Done!');
},
);
}
homeview calls homecontroller
Try using map for transforming Streams:
#override
Stream<dynamic> subscribe({
String query,
Map<String, dynamic> variables,
}) {
debugPrint('===->subscribe===');
// it can be any stream here, http or file or image or media
final Stream<GraphQLResponse<String>> operation = Amplify.API.subscribe(
GraphQLRequest<String>(
document: query,
variables: variables,
),
onEstablished: () {
debugPrint(
'===->subscribe onEstablished ===',
);
},
);
return operation.map((event) {
return json.decode(event.data);
});
}
// elsewhere
final subscription = subscribe(
query: 'some query',
variables: {},
);
subscription.listen(
(jsonData) {
debugPrint('===->subscription data $jsonData');
},
onError: (Object e) => debugPrint('Error in subscription stream: $e'),
);
1. OBJECTIVE
I would like the connection between my custom WebSocket server (API) and my Flutter app, to be re-established automatically when encountering network issues or when the WebSocket server encounter issues.
Use case 1: the wifi stops and suddenly comes back.
Use case 2: the API is not started and restarts suddenly.
Constraint: I use Riverpod as a state management library (and I want to keep it :)).
I emphasize about the state management library because I create the WS connection in a StreamProvider (cf. Riverpod).
2. INITIAL SETUP WITHOUT AUTOMATIC RECONNECT
I created a StreamProvider as shown below:
final hostProvider =
StreamProvider.autoDispose.family<Host, String>((ref, ip) async* {
//SOCKET OPEN
final channel = IOWebSocketChannel.connect('ws://$ip:$port/v1/path');
ref.onDispose(() {
// SOCKET CLOSE
return channel.sink.close();
});
await for (final json in channel.stream) {
final jsonStr = jsonDecode(json as String);
yield Host.fromJson(jsonStr as Map<String, dynamic>);
}
});
And I created a widget to consume the data:
useProvider(hostProvider(ip)).when(
data: (data) => show the result
loading: () => show progress bar
error: (error, _) => show error
);
This piece of code works great. However, there is no automatic reconnect mechanism.
3. AUTOMATIC RECONNECT ATTEMPTS
I called a function connectWs in a try/catch whenever exceptions are caught:
final hostProvider =
StreamProvider.autoDispose.family<Host, String>((ref, ip) async* {
// Open the connection
connectWs('ws://$ip:$port/v1/path').then((value) async* {
final channel = IOWebSocketChannel(value);
ref.onDispose(() {
return channel.sink.close();
});
await for (final json in channel.stream) {
final jsonStr = jsonDecode(json as String);
yield Host.fromJson(jsonStr as Map<String, dynamic>);
}
});
});
Future<WebSocket> connectWs(String path) async {
try {
return await WebSocket.connect(path);
} catch (e) {
print("Error! " + e.toString());
await Future.delayed(Duration(milliseconds: 2000));
return await connectWs(path);
}
}
I created a connectProvider provider, as shown here below, I 'watched' in hostProvider in order to create a channel. Whenever there is an exception, I use the refresh function from the Riverpod library to recreate the channel:
// used in hostProvider
ref.container.refresh(connectProvider(ip))
final connectProvider =
Provider.family<Host, String>((ref, ip) {
//SOCKET OPEN
return IOWebSocketChannel.connect('ws://$ip:$port/v1/path');
});
Thanks in advance for your help.
Thanks, #Dewey.
In the end, I found a workaround that works for my use case:
My providers: channelProvider & streamProvider
static final channelProvider = Provider.autoDispose
.family<IOWebSocketChannel, HttpParam>((ref, httpParam) {
log.i('channelProvider | Metrics - $httpParam');
return IOWebSocketChannel.connect(
'ws://${httpParam.ip}:$port/v1/${httpParam.path}');
});
static final streamProvider =
StreamProvider.autoDispose.family<dynamic, HttpParam>((ref, httpParam) {
log.i('streamProvider | Metrics - $httpParam');
log.i('streamProvider | Metrics - socket ${httpParam.path} opened');
var bStream = ref
.watch(channelProvider(httpParam))
.stream
.asBroadcastStream(onCancel: (sub) => sub.cancel());
var isSubControlError = false;
final sub = bStream.listen(
(data) {
ref
.watch(channelProvider(httpParam))
.sink
?.add('> sink add ${httpParam.path}');
},
onError: (_, stack) => null,
onDone: () async {
isSubControlError = true;
await Future.delayed(Duration(seconds: 10));
ref.container.refresh(channelProvider(httpParam));
},
);
ref.onDispose(() {
log.i('streamProvider | Metrics - socket ${httpParam.path} closed');
sub.cancel();
if (isSubControlError == false)
ref.watch(channelProvider(httpParam)).sink?.close(1001);
bStream = null;
});
return bStream;
});
I consume streamProvider that way in my widget:
return useProvider(MetricsWsRepository.streamProvider(HttpParam(
ip: ip,
path: 'dummy-path',
))).when(
data: (data) => deserialize & doSomething1,
loading:() => doSomething2,
error: (_, stack) => doSomething3
)
I'm a bit of a beginner with riverpod but it seems to me you want to use a higher-level redux/bloc style flow to recreate the provider each time it fails ...
This higher level bloc creates the provider when the connection succeeds, and when the connection fails, you dispatch an event to the bloc that tells it to reconnect and recreate the provider ...
That's my thought, but again, I'm a beginner with this package.