這是我參與8月更文挑戰的第18天,活動詳情檢視:8月更文挑戰
前言
在實際的應用中,我們經常會遇到資料流的處理,比如監聽實時位置時會產生實時的位置資訊流,比如 Socket
通訊時需要監聽Socket
資料流。對於資料流,在 Flutter
中稱之為 Stream
,而 Provider
為 Stream
專門設計了一個 StreamProvider
來監聽資料流的變化,當資料流產生新的資料時就會通知監聽元件重新整理。本篇我們就來介紹如何利用 StreamProvider
監聽 WebSocket
資料。
本篇設計的知識點如下:
WebSokcet
客戶端封裝外掛socket_io_client
的使用。StreamProvider
監聽 Socket資料。
為了能夠讓程式正常執行,已經在後端程式碼增加了簡單的 socket
,使用的是和 socket_io_client
配套的服務端socket.io
,npm
地址為socket.io,版本為4.1.3。注意 Flutter 的外掛存在版本相容性,配套該版本的當前處於開發中的版本:^2.0.0-beta.4-nullsafety.0
(穩定版的1.0.1外掛不支援服務端的4.1.3版本)。後端原始碼地址為:後端配套原始碼。
socket_io_client 介紹
socket_io_client 是廣受歡迎的 Socket 客戶端外掛,javascript 也有對應的版本。socket_io_client 對 Dart 的 Socket 進行了封裝,從而簡化了 socket 的使用。
建立連線的方式如下,其中$host
和$port
對應主機名和埠,後面的是配置引數。使用websocket
時,需要指定 transports
(陣列)的元素包括 websocket
,autoConnect
為建立後是否自動連線,預設會自動連線。forceNew
為是否每次建立新的 Socket
物件,如果為 false
(預設) 則會使用舊的連線(沒有關閉的情況下)。這裡我們使用的是每次使用新的 Socket
物件 這是因為退出頁面後我們要關閉連線。
_socket = SocketIO.io('ws://$host:$port', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': true,
'forceNew': true
});
複製程式碼
建立好之後就可以設定 Socket
的事件響應回撥方法,常用的由如下方法:
onConnect(EventHandler handler)
:連線建立成功回撥;onConnectTimeout(EventHandler handler)
:連線超時回撥;onConnectError(EventHandler handler)
:連線錯誤回撥;onError(EventHandler handler)
:發生錯誤時回撥;on(String event, (EventHandler handler)
:訂閱指定事件的訊息,服務端傳送該事件的訊息時可以在該函式接收。onDisconnect(EventHandler handler)
:斷開連線時回撥;connect/disconnect
:主動連線/斷開連線方法;open/close
:開啟和關閉方法。
重點是建立連線後,當接收到的資料加入到一個流處理物件中。Flutter
提供了StreamController
類處理 Stream
物件。StreamController
只允許有一個訂閱者,可以使用 sink
屬性的 add
方法新增新的流資料,完成新增後會通知其訂閱者有新的資料產生。因此我們可以使用 StreamProvider
來訂閱這個流資料。
我們構建一個 Socket 管理類 StreamSocket,程式碼如下:
class StreamSocket {
final _socketResponse = StreamController<String>();
Stream<String> get getResponse => _socketResponse.stream;
final String host;
final int port;
late final Socket _socket;
StreamSocket({required this.host, required this.port}) {
_socket = SocketIO.io('ws://$host:$port', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': true,
'forceNew': true
});
}
void connectAndListen() {
_socket.onConnect((_) {
debugPrint('connected');
});
_socket.onConnectTimeout((data) => debugPrint('timeout'));
_socket.onConnectError((error) => debugPrint(error.toString()));
_socket.onError((error) => debugPrint(error.toString()));
_socket.on('msg', (data) {
_socketResponse.sink.add(data);
});
_socket.onDisconnect((_) => debugPrint('disconnect'));
}
void sendTextMessage(String message) {
_socket.emit('msg', message);
}
void close() {
_socketResponse.close();
_socket.disconnect().close();
}
}
複製程式碼
StreamProvider 應用
StreamProvider 的應用和 FutureProvider,ChangeNotifierProvider 類似,也分為create 和 value兩種形式:
// create 形式
StreamProvider({
Key? key,
required Create<Stream<T>?> create,
required T initialData,
ErrorBuilder<T>? catchError,
UpdateShouldNotify<T>? updateShouldNotify,
bool? lazy,
TransitionBuilder? builder,
Widget? child,
})
// value 形式
StreamProvider.value({
Key? key,
required Stream<T>? value,
required T initialData,
ErrorBuilder<T>? catchError,
UpdateShouldNotify<T>? updateShouldNotify,
bool? lazy,
TransitionBuilder? builder,
Widget? child,
})
複製程式碼
這裡我們首先使用一個 StatefulWidget
來管理 StreamSocket
的初始化以及 Socket
的關閉,並將實際業務元件用兩個 Provider
包裹,一個是 StreamProvider
,一個是 MessageModel
。StreamProvider
用於自動監聽 Socket
的資料流,並顯示在介面上;MessageModel
目前僅用於傳送訊息。
class _SocketClientWrapperState extends State<SocketClientWrapper> {
final StreamSocket streamSocket = StreamSocket(host: '127.0.0.1', port: 3001);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stream Provicer'),
),
body: Stack(
alignment: Alignment.bottomCenter,
children: [
StreamProvider<String>(
create: (context) => streamSocket.getResponse,
initialData: '',
child: StreamDemo(),
),
ChangeNotifierProvider<MessageModel>(
child: MessageReplyBar(messageSendHandler: (message) {
streamSocket.sendTextMessage(message);
}),
create: (context) => MessageModel(),
),
],
),
);
}
@override
void initState() {
streamSocket.connectAndListen();
super.initState();
}
@override
void dispose() {
streamSocket.close();
super.dispose();
}
}
複製程式碼
這裡我們使用了 Stack將訊息的輸入條MessageReplyBar
固定在底部,需要注意的是,如果想要輸入條自動隨著鍵盤浮動,則需要將其放入到 Scaffold
的body
裡,否則會一直固定在底部而被遮擋住。
StreamDemo
和MessageReplyBar
兩個元件都很簡單,業務邏輯為 MessageReplyBar
點選傳送按鈕將訊息通過Socket
發出到服務端。然後 StreamDemo
回顯 StreamProvider
從 Socket
接收到的服務端訊息。
執行效果
我們配合服務端的列印日誌來看執行效果:可以看到連線的建立、傳送訊息、接收訊息和斷開連線的過程都是正常的。
原始碼已上傳至:狀態管理相關程式碼 - null safety 版本。
總結
本篇介紹了Provider
的 StreamProvider
流狀態管理,同時引入了 socket_io_client
外掛實現了與服務端的 WebSocket
通訊,通過StreamProvider
訂閱流資料,並通知介面重新整理,實現了介面層的無侵入Socket
資料展示。這一篇比較簡單,更多地是介紹兩個工具的使用,下一篇我們用這兩個工具來實現一個即時聊天應用。
我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章,對應原始碼請看這裡:Flutter 入門與實戰專欄原始碼。
??:覺得有收穫請點個贊鼓勵一下!
?:收藏文章,方便回看哦!
?:評論交流,互相進步!