1+ import 'dart:isolate' ;
2+ import 'dart:io' ;
13import 'package:grpc/grpc.dart' ;
24
35import 'api/v1/chat.pbgrpc.dart' as grpc;
@@ -7,19 +9,25 @@ import 'chat_message.dart';
79import 'chat_message_outgoing.dart' ;
810
911/// CHANGE TO IP ADDRESS OF YOUR SERVER IF IT IS NECESSARY
10- const serverIP = "127 .0.0.1 " ;
12+ const serverIP = "10 .0.2.2 " ;
1113const serverPort = 3000 ;
1214
1315/// ChatService client implementation
1416class ChatService {
15- /// Flag is indicating that client is shutting down
16- bool _isShutdown = false ;
17+ // _isolateSending is isolate to send chat messages
18+ Isolate _isolateSending ;
1719
18- /// gRPC client channel to send messages to the server
19- ClientChannel _clientSend ;
20+ // Port to send message
21+ SendPort _portSending ;
2022
21- /// gRPC client channel to receive messages from the server
22- ClientChannel _clientReceive;
23+ // Port to get status of message sending
24+ ReceivePort _portSendStatus;
25+
26+ // _isolateReceiving is isolate to receive chat messages
27+ Isolate _isolateReceiving;
28+
29+ // Port to receive messages
30+ ReceivePort _portReceiving;
2331
2432 /// Event is raised when message has been sent to the server successfully
2533 final void Function (MessageOutgoing message) onSentSuccess;
@@ -38,117 +46,192 @@ class ChatService {
3846 {this .onSentSuccess,
3947 this .onSentError,
4048 this .onReceivedSuccess,
41- this .onReceivedError});
42-
43- // Shutdown client
44- Future <void > shutdown () async {
45- _isShutdown = true ;
46- _shutdownSend ();
47- _shutdownReceive ();
49+ this .onReceivedError})
50+ : _portSendStatus = ReceivePort (),
51+ _portReceiving = ReceivePort ();
52+
53+ // Start threads to send and receive messages
54+ void start () {
55+ _startSending ();
56+ _startReceiving ();
4857 }
4958
50- // Shutdown client (send channel)
51- void _shutdownSend () {
52- if (_clientSend != null ) {
53- _clientSend.shutdown ();
54- _clientSend = null ;
59+ /// Start thread to send messages
60+ void _startSending () async {
61+ // start thread to send messages
62+ _isolateSending =
63+ await Isolate .spawn (_sendingIsolate, _portSendStatus.sendPort);
64+
65+ // listen send status
66+ await for (var msg in _portSendStatus) {
67+ if (msg is SendPort ) {
68+ _portSending = msg;
69+ } else {
70+ var message = msg[0 ] as MessageOutgoing ;
71+ var statusDetails = msg[1 ] as String ;
72+ switch (message.status) {
73+ case MessageOutgoingStatus .SENT :
74+ // call for success handler
75+ if (onSentSuccess != null ) {
76+ onSentSuccess (message);
77+ }
78+ break ;
79+ case MessageOutgoingStatus .FAILED :
80+ // call for error handler
81+ if (onSentError != null ) {
82+ onSentError (message, statusDetails);
83+ }
84+ break ;
85+ default :
86+ // call for error handler
87+ if (onSentError != null ) {
88+ onSentError (message, "unexpected message status" );
89+ }
90+ break ;
91+ }
92+ }
5593 }
5694 }
5795
58- // Shutdown client (receive channel)
59- void _shutdownReceive () {
60- if (_clientReceive != null ) {
61- _clientReceive.shutdown ();
62- _clientReceive = null ;
63- }
64- }
96+ /// Thread to send messages
97+ static void _sendingIsolate (SendPort portSendStatus) async {
98+ // Port to get messages to send
99+ ReceivePort portSendMessages = ReceivePort ();
100+
101+ // send port to send messages to the caller
102+ portSendStatus.send (portSendMessages.sendPort);
103+
104+ ClientChannel client;
105+
106+ // waiting messages to send
107+ await for (MessageOutgoing message in portSendMessages) {
108+ var sent = false ;
109+ do {
110+ if (client == null ) {
111+ // create new client
112+ client = ClientChannel (
113+ serverIP, // Your IP here or localhost
114+ port: serverPort,
115+ options: ChannelOptions (
116+ //TODO: Change to secure with server certificates
117+ credentials: ChannelCredentials .insecure (),
118+ idleTimeout: Duration (seconds: 1 ),
119+ ),
120+ );
121+ }
65122
66- /// Send message to the server
67- void send (MessageOutgoing message) {
68- if (_clientSend == null ) {
69- // create new client
70- _clientSend = ClientChannel (
71- serverIP, // Your IP here or localhost
72- port: serverPort,
73- options: ChannelOptions (
74- //TODO: Change to secure with server certificates
75- credentials: ChannelCredentials .insecure (),
76- idleTimeout: Duration (seconds: 10 ),
77- ),
78- );
79- }
123+ MessageOutgoingStatus statusCode;
124+ String statusDetails;
125+
126+ try {
127+ // try to send
128+ var request = StringValue .create ();
129+ request.value = message.text;
130+ await grpc.ChatServiceClient (client).send (request);
131+ // sent successfully
132+ statusCode = MessageOutgoingStatus .SENT ;
133+ sent = true ;
134+ } catch (e) {
135+ // sent failed
136+ statusCode = MessageOutgoingStatus .FAILED ;
137+ statusDetails = e.toString ();
138+ // reset client
139+ client.shutdown ();
140+ client = null ;
141+ } finally {
142+ var msg = MessageOutgoing (
143+ text: message.text, id: message.id, status: statusCode);
144+ portSendStatus.send ([
145+ msg,
146+ statusDetails,
147+ ]);
148+ }
80149
81- var request = StringValue .create ();
82- request.value = message.text;
83-
84- grpc.ChatServiceClient (_clientSend).send (request).then ((_) {
85- // call for success handler
86- if (onSentSuccess != null ) {
87- var sentMessage = MessageOutgoing (
88- text: message.text,
89- id: message.id,
90- status: MessageOutgoingStatus .SENT );
91- onSentSuccess (sentMessage);
92- }
93- }).catchError ((e) {
94- if (! _isShutdown) {
95- // invalidate current client
96- _shutdownSend ();
150+ if (! sent) {
151+ // try to send again
152+ sleep (Duration (seconds: 5 ));
153+ }
154+ } while (! sent);
155+ }
156+ }
97157
158+ /// Start listening messages from the server
159+ void _startReceiving () async {
160+ // start thread to receive messages
161+ _isolateReceiving =
162+ await Isolate .spawn (_receivingIsolate, _portReceiving.sendPort);
163+
164+ // listen for incoming messages
165+ await for (var msg in _portReceiving) {
166+ var message = msg[0 ] as Message ;
167+ var error = msg[1 ] as String ;
168+ if (error != null ) {
98169 // call for error handler
99- if (onSentError != null ) {
100- onSentError (message, e.toString ());
170+ if (onReceivedError != null ) {
171+ onReceivedError (error);
172+ }
173+ } else {
174+ if (onReceivedSuccess != null ) {
175+ onReceivedSuccess (message);
101176 }
102-
103- // try to send again
104- Future .delayed (Duration (seconds: 30 ), () {
105- send (message);
106- });
107177 }
108- });
178+ }
109179 }
110180
111- /// Start listening messages from the server
112- void startListening () {
113- if (_clientReceive == null ) {
114- // create new client
115- _clientReceive = ClientChannel (
116- serverIP, // Your IP here or localhost
117- port: serverPort,
118- options: ChannelOptions (
119- //TODO: Change to secure with server certificates
120- credentials: ChannelCredentials .insecure (),
121- idleTimeout: Duration (seconds: 10 ),
122- ),
123- );
124- }
181+ /// Thread to listen messages from the server
182+ static void _receivingIsolate (SendPort portReceive) async {
183+ ClientChannel client;
184+
185+ do {
186+ if (client == null ) {
187+ // create new client
188+ client = ClientChannel (
189+ serverIP, // Your IP here or localhost
190+ port: serverPort,
191+ options: ChannelOptions (
192+ //TODO: Change to secure with server certificates
193+ credentials: ChannelCredentials .insecure (),
194+ idleTimeout: Duration (seconds: 1 ),
195+ ),
196+ );
197+ }
125198
126- var stream =
127- grpc.ChatServiceClient (_clientReceive).subscribe (Empty .create ());
199+ var stream = grpc.ChatServiceClient (client).subscribe (Empty .create ());
128200
129- stream.forEach ((msg) {
130- if (onReceivedSuccess != null ) {
131- var message = Message (msg.text);
132- onReceivedSuccess (message);
201+ try {
202+ await for (var msg in stream) {
203+ var message = Message (msg.text);
204+ portReceive.send ([message, null ]);
205+ }
206+ } catch (e) {
207+ // reset client
208+ client.shutdown ();
209+ client = null ;
210+ // notify caller
211+ portReceive.send ([null , e.toString ()]);
133212 }
134- }).then ((_) {
135- // raise exception to start listening again
136- throw Exception ("stream from the server has been closed" );
137- }).catchError ((e) {
138- if (! _isShutdown) {
139- // invalidate current client
140- _shutdownReceive ();
213+ // try to connect again
214+ sleep (Duration (seconds: 5 ));
215+ } while (true );
216+ }
141217
142- // call for error handler
143- if (onReceivedError != null ) {
144- onReceivedError (e.toString ());
145- }
218+ // Shutdown client
219+ void shutdown () {
220+ // stop sending
221+ _isolateSending? .kill (priority: Isolate .immediate);
222+ _isolateSending = null ;
223+ _portSendStatus? .close ();
224+ _portSendStatus = null ;
225+
226+ // stop receiving
227+ _isolateReceiving? .kill (priority: Isolate .immediate);
228+ _isolateReceiving = null ;
229+ _portReceiving? .close ();
230+ _portReceiving = null ;
231+ }
146232
147- // start listening again
148- Future .delayed (Duration (seconds: 30 ), () {
149- startListening ();
150- });
151- }
152- });
233+ /// Send message to the server
234+ void send (MessageOutgoing message) {
235+ _portSending.send (message);
153236 }
154237}
0 commit comments