<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們在前面花了很大篇幅介紹 Provider
狀態管理,這是因為在 Flutter 中,Provider
是眾多狀態管理外掛的首選。本篇以即時聊天為例,來講述 Provider
的綜合應用,也算是 Provider
狀態管理系列的終結篇。本篇涉及的內容如下:
我們在聊天前,需要選擇對應的聯絡人進行單聊,因此需要構建一個聯絡人列表。這裡我們使用簡單的 ListView.builder+Mock 資料構建聯絡人列表。介面如下所示,其中關鍵的就是點選聯絡人時將聯絡人的 id通過路由傳遞過去,以便傳送訊息時通過使用者 id指定接收使用者。
return ListTile( leading: _getRoundImage(contactors[index].avatar, 50), title: Text(contactors[index].nickname), subtitle: Text( contactors[index].description, style: TextStyle(fontSize: 14.0, color: Colors.grey), ), onTap: () { debugPrint(contactors[index].id); RouterManager.router.navigateTo(context, '${RouterManager.chatPath}?toUserId=${contactors[index].id}'); }, );
我們將傳送的訊息放在右邊,將接收到的訊息放在左邊,居左還是居右通過 Container
的 margin
來實現。至於區分,通過訊息物件的fromUserId
來區分,如果 fromUserId
和當前使用者id
一致,則是傳送出去的訊息,否則就是接收到的訊息。在這裡我們因為還沒有使用者體系,先將當前的使用者 id
寫死。為了實現模擬器之間的聊天,我們一個模擬器設定為 user1
,一個設定為 user2
。介面也是使用ListView.builder
(萬能不?)構建。
return ListView.builder( itemBuilder: (context, index) { MessageEntity message = messages[index]; double margin = 20; double marginLeft = message.fromUserId == 'user1' ? 60 : margin; double marginRight = message.fromUserId == 'user1' ? margin : 60; return Container( margin: EdgeInsets.fromLTRB(marginLeft, margin, marginRight, margin), padding: EdgeInsets.all(15), alignment: message.fromUserId == 'user1' ? Alignment.centerRight : Alignment.centerLeft, decoration: BoxDecoration( color: message.fromUserId == 'user1' ? Colors.green[300] : Colors.blue[400], borderRadius: BorderRadius.circular(10)), child: Text( message.content, style: TextStyle(color: Colors.white), ), ); }, itemCount: messages.length, );
聊天介面的一個特點是會接收StreamProvider
推播的最新的訊息,為了統一,我們將接收訊息和傳送訊息都通過StreamProvider
推播更新介面。
// 傳送訊息時將訊息加入到流控制器中 void sendMessage(String event, T message) { _socket.emit(event, message); _socketResponse.sink.add(message); } // 接收訊息時也加入到流控制器中 _socket.on(recvEvent, (data) { _socketResponse.sink.add(data); });
這樣不管是接收訊息還是傳送訊息都會通過 StreamProvider
重新構建聊天介面。那問題來了,聊天列表資料如何重新整理呢?
訊息介面需要接收 StreamProvider
的訊息流,還需要使用訊息列表資料,這裡我們使用了 MultiProvider
。其中訊息傳送框和聊天介面共用 ChatMessageModel
(僅為演示,實際可以拆分開)。
final chatMessageModel = ChatMessageModel(); //... body: Stack( alignment: Alignment.bottomCenter, children: [ MultiProvider( providers: [ StreamProvider<Map<String, dynamic>?>( create: (context) => streamSocket.getResponse, initialData: null), ChangeNotifierProvider.value(value: chatMessageModel) ], child: StreamDemo(), ), ChangeNotifierProvider.value( child: MessageReplyBar(messageSendHandler: (message) { Map<String, String> json = { 'fromUserId': 'user1', 'toUserId': widget.toUserId, 'contentType': 'text', 'content': message }; streamSocket.sendMessage('chat', json); }), value: chatMessageModel, ), ] //...
其中ChatMessageModel
即訊息列表狀態資料,裡面只有一個訊息物件陣列和一個新增訊息方法,以及一個 content
屬性是給訊息回覆框使用的。
這裡就有一個問題,StreamProvider
推播 StreamSocket
過來的訊息的時候, ChatMessageModel
其實是不知道的。如果要知道,一個辦法就是在 StreamSocket
參照 ChatMessageModel
物件,然後呼叫其 addMessage
方法新增訊息。但是這樣會增加兩個類的耦合。還有一種方式是取巧的方式了,那就是 StreamdDemo
的 build
方法能夠獲取到 StreamSocket
推播的最新訊息,在這裡讀取到最新的訊息後就可以新增到訊息列表了。由於前面我們傳送訊息和接收訊息都將訊息加入到了訊息流中,這樣處理方式就統一了。
這種方式需要注意,Provider
不允許在元件的build
方法中再次呼叫類似 notifyListeners
的方法通知該元件重新整理,因此在 ChatMessageModel
的 addMessage
方法裡不可以使用notifyListeners
來通知元件重新整理,否則會出現同一元件重新整理衝突。實際上,因為另一個Provider
已經通知該元件重新整理了,因此也沒必要再通知了。當然,這僅僅是一種取巧方法,假設這個addMessage
方法還需要通知其他元件重新整理,那這種形式就就不可取了。
class ChatMessageModel with ChangeNotifier { List<MessageEntity> _messages = []; List<MessageEntity> get messages => _messages; String content = ''; void addMessage(Map<String, dynamic> json) { _messages.add(MessageEntity.fromJson(json)); } }
這裡我們先不考慮這種情況,StreamDemo
的 build
關於這部分的處理方法如下,這裡對於吧 ChatMessageModel
也就不需要使用 watch
方法了,完全依賴於 StreamProvider
的流推播來更新元件。每次傳送訊息或接收訊息後,構建時在返回元件樹前就更新了訊息列表資料了,因此也能保證資料是最新的。其實,相當於我們投機取巧實現了兩個 Provider
之間的資料互動。
@override Widget build(BuildContext context) { Map<String, dynamic>? messageJson = context.watch<Map<String, dynamic>?>(); if (messageJson != null) { context.read<ChatMessageModel>().addMessage(messageJson); } List<MessageEntity> messages = context.read<ChatMessageModel>().messages; // ListView 部分 }
來看一下執行效果,模擬器的好處就是可以開多個偵錯。效果是實現了,不過實際即時聊天比這個複雜很多,而且一般也不會用 Socket
,但是如果 App 內部要實現應用開啟後的即時訊息推播,WebSocket
是一個不錯的選擇。原始碼已經提交,後端和Flutter 程式碼分佈如下:
Flutter Provider 部分程式碼(null safety 版本)
後端程式碼(Express 版本)
以上就是Android Flutter基於WebSocket實現即時通訊功能的詳細內容,更多關於Flutter WebSocket通訊的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45