Android Flutter基于WebSocket實(shí)現(xiàn)即時(shí)通訊功能
前言
我們?cè)谇懊婊撕艽笃榻B Provider
狀態(tài)管理,這是因?yàn)樵?Flutter 中,Provider
是眾多狀態(tài)管理插件的首選。本篇以即時(shí)聊天為例,來講述 Provider
的綜合應(yīng)用,也算是 Provider
狀態(tài)管理系列的終結(jié)篇。本篇涉及的內(nèi)容如下:
- 聯(lián)系人界面的構(gòu)建;
- 聊天界面的簡(jiǎn)單實(shí)現(xiàn);
- StreamProvider 接收 Socket流數(shù)據(jù)并自動(dòng)通知界面刷新;
- MultiProvider為聊天主界面提供多個(gè)Provider狀態(tài);
- 多個(gè) Provider存在交叉數(shù)據(jù)時(shí)處理方式。
聯(lián)系人界面構(gòu)建
我們?cè)诹奶烨?,需要選擇對(duì)應(yīng)的聯(lián)系人進(jìn)行單聊,因此需要構(gòu)建一個(gè)聯(lián)系人列表。這里我們使用簡(jiǎn)單的 ListView.builder+Mock 數(shù)據(jù)構(gòu)建聯(lián)系人列表。界面如下所示,其中關(guān)鍵的就是點(diǎn)擊聯(lián)系人時(shí)將聯(lián)系人的 id通過路由傳遞過去,以便發(fā)送消息時(shí)通過用戶 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}'); ??}, );
聊天界面的實(shí)現(xiàn)
我們將發(fā)送的消息放在右邊,將接收到的消息放在左邊,居左還是居右通過 Container
的 margin
來實(shí)現(xiàn)。至于區(qū)分,通過消息對(duì)象的fromUserId
來區(qū)分,如果 fromUserId
和當(dāng)前用戶id
一致,則是發(fā)送出去的消息,否則就是接收到的消息。在這里我們因?yàn)檫€沒有用戶體系,先將當(dāng)前的用戶 id
寫死。為了實(shí)現(xiàn)模擬器之間的聊天,我們一個(gè)模擬器設(shè)置為 user1
,一個(gè)設(shè)置為 user2
。界面也是使用ListView.builder
(萬能不?)構(gòu)建。
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, );
聊天界面的一個(gè)特點(diǎn)是會(huì)接收StreamProvider
推送的最新的消息,為了統(tǒng)一,我們將接收消息和發(fā)送消息都通過StreamProvider
推送更新界面。
//?發(fā)送消息時(shí)將消息加入到流控制器中 void?sendMessage(String?event,?T?message)?{ ??_socket.emit(event,?message); ??_socketResponse.sink.add(message); } //?接收消息時(shí)也加入到流控制器中 _socket.on(recvEvent,?(data)?{ ??_socketResponse.sink.add(data); });
這樣不管是接收消息還是發(fā)送消息都會(huì)通過 StreamProvider
重新構(gòu)建聊天界面。那問題來了,聊天列表數(shù)據(jù)如何刷新呢?
消息界面的 MultiProvider
消息界面需要接收 StreamProvider
的消息流,還需要使用消息列表數(shù)據(jù),這里我們使用了 MultiProvider
。其中消息發(fā)送框和聊天界面共用 ChatMessageModel
(僅為演示,實(shí)際可以拆分開)。
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
即消息列表狀態(tài)數(shù)據(jù),里面只有一個(gè)消息對(duì)象數(shù)組和一個(gè)添加消息方法,以及一個(gè) content
屬性是給消息回復(fù)框使用的。
這里就有一個(gè)問題,StreamProvider
推送 StreamSocket
過來的消息的時(shí)候, ChatMessageModel
其實(shí)是不知道的。如果要知道,一個(gè)辦法就是在 StreamSocket
引用 ChatMessageModel
對(duì)象,然后調(diào)用其 addMessage
方法添加消息。但是這樣會(huì)增加兩個(gè)類的耦合。還有一種方式是取巧的方式了,那就是 StreamdDemo
的 build
方法能夠獲取到 StreamSocket
推送的最新消息,在這里讀取到最新的消息后就可以添加到消息列表了。由于前面我們發(fā)送消息和接收消息都將消息加入到了消息流中,這樣處理方式就統(tǒng)一了。
這種方式需要注意,Provider
不允許在組件的build
方法中再次調(diào)用類似 notifyListeners
的方法通知該組件刷新,因此在 ChatMessageModel
的 addMessage
方法里不可以使用notifyListeners
來通知組件刷新,否則會(huì)出現(xiàn)同一組件刷新沖突。實(shí)際上,因?yàn)榱硪粋€(gè)Provider
已經(jīng)通知該組件刷新了,因此也沒必要再通知了。當(dāng)然,這僅僅是一種取巧方法,假設(shè)這個(gè)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
關(guān)于這部分的處理方法如下,這里對(duì)于吧 ChatMessageModel
也就不需要使用 watch
方法了,完全依賴于 StreamProvider
的流推送來更新組件。每次發(fā)送消息或接收消息后,構(gòu)建時(shí)在返回組件樹前就更新了消息列表數(shù)據(jù)了,因此也能保證數(shù)據(jù)是最新的。其實(shí),相當(dāng)于我們投機(jī)取巧實(shí)現(xiàn)了兩個(gè) Provider
之間的數(shù)據(jù)交互。
@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?部分 }
運(yùn)行效果
來看一下運(yùn)行效果,模擬器的好處就是可以開多個(gè)調(diào)試。效果是實(shí)現(xiàn)了,不過實(shí)際即時(shí)聊天比這個(gè)復(fù)雜很多,而且一般也不會(huì)用 Socket
,但是如果 App 內(nèi)部要實(shí)現(xiàn)應(yīng)用打開后的即時(shí)消息推送,WebSocket
是一個(gè)不錯(cuò)的選擇。源碼已經(jīng)提交,后端和Flutter 代碼分布如下:
Flutter Provider 部分代碼(null safety 版本)
后端代碼(Express 版本)
以上就是Android Flutter基于WebSocket實(shí)現(xiàn)即時(shí)通訊功能的詳細(xì)內(nèi)容,更多關(guān)于Flutter WebSocket通訊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Android 10/Android Q手機(jī)在后臺(tái)無法正常定位問題
這篇文章主要介紹了解決Android 10/Android Q手機(jī)在后臺(tái)無法正常定位問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11Android實(shí)現(xiàn)長(zhǎng)按圓環(huán)動(dòng)畫View效果的思路代碼
這篇文章主要介紹了Android實(shí)現(xiàn)長(zhǎng)按圓環(huán)動(dòng)畫View效果,本文給大家分享實(shí)現(xiàn)思路,通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09FFmpeg?Principle學(xué)習(xí)new_video_stream添加視頻輸出流
這篇文章主要為大家介紹了FFmpeg?Principle學(xué)習(xí)new_video_stream添加視頻輸出流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Android 調(diào)用系統(tǒng)相冊(cè)選擇照片
這篇文章主要介紹了Android 調(diào)用系統(tǒng)相冊(cè)選擇照片的方法,幫助大家更好的進(jìn)行Android開發(fā),感興趣的朋友可以了解下2020-12-12Android 獲取內(nèi)外SD卡路徑幾種方法總結(jié)
這篇文章主要介紹了Android 獲得內(nèi)外SD卡路徑幾種方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2016-12-12Android實(shí)現(xiàn)GridView中的item自由拖動(dòng)效果
在前一個(gè)項(xiàng)目中,實(shí)現(xiàn)了一個(gè)功能是gridview中的item自由拖到效果,實(shí)現(xiàn)思路很簡(jiǎn)單,主要工作就是交換節(jié)點(diǎn),以及拖動(dòng)時(shí)的移動(dòng)效果,下面小編給大家分享具體實(shí)現(xiàn)過程,對(duì)gridview實(shí)現(xiàn)拖拽效果感興趣的朋友一起看看吧2016-11-11在Android里完美實(shí)現(xiàn)基站和WIFI定位
眾所周知的,在OPhone和大部分國產(chǎn)的Android定制機(jī)里不支持最簡(jiǎn)單實(shí)用的基站和WIFI定位,只能使用速度慢而耗電的GPS定位,但OPhone和華為/中興生產(chǎn)的一些Android定制機(jī)卻占據(jù)了一定的市場(chǎng),因此導(dǎo)致了很多使用了定位技術(shù)的Andorid應(yīng)用挺尷尬的。2014-07-07Android通過startService實(shí)現(xiàn)文件批量下載
這篇文章主要為大家詳細(xì)介紹了Android通過startService實(shí)現(xiàn)文件批量下載的示例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12Android實(shí)現(xiàn)登錄注冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)登錄注冊(cè)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04