Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。
效果图:
详细的聊天功能请查看Kotlin高仿微信-第8篇-单聊,这里是提取小视频功能的部分实现。
实现代码:
<com.wn.wechatclientdemo.view.SVideoView android:id="@+id/chat_item_me_video" app:layout_constraintEnd_toStartOf="@+id/chat_item_me_avatar" app:layout_constraintTop_toTopOf="@+id/chat_item_me_avatar" android:layout_width="160dp" android:layout_height="240dp" android:layout_marginEnd="10dp" android:visibility="gone"/>
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/24 15:57 * Description : 小视频播放 */ class SVideoView : LinearLayout, SurfaceHolder.Callback, OnPreparedListener, OnCompletionListener, OnErrorListener, OnInfoListener, View.OnClickListener, OnSeekCompleteListener, OnVideoSizeChangedListener, OnSeekBarChangeListener{ private var playOrPauseIv: ImageView? = null private var videoSuf: SurfaceView? = null private var mPlayer: MediaPlayer? = null private var mSeekBar: SeekBar? = null private var path: String? = null private var rootViewRl: RelativeLayout? = null private var controlLl: LinearLayout? = null private var startTime: TextView? = null private var endTime:TextView? = null private var secondTime:TextView? = null private var forwardButton: ImageView? = null private var backwardButton: ImageView? = null private var svideoThumbnail: ImageView? = null private var isShow = false private var chatCircleProgress: ChatCircleProgress? = null val UPDATE_TIME = 0X0001 val HIDE_CONTROL = 0X0002 private var view : View? = null private var isAutoPlay: Boolean = false private val mHandler: Handler = object : Handler() { override fun handleMessage(msg: Message) { super.handleMessage(msg) when (msg.what) { UPDATE_TIME -> { updateTime() sendEmptyMessageDelayed(UPDATE_TIME, 500) } HIDE_CONTROL -> hideControl() } } } constructor(context: Context) : this(context, null) constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0) constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr) init { view = LayoutInflater.from(WcApp.getContext()).inflate(R.layout.wc_svideo_view, this) initViews() } fun processVideo(){ initSurfaceView() initPlayer() initEvent() getVideoRoration(path) } fun onDestroy() { videoSuf = null mHandler.removeMessages(HIDE_CONTROL); mHandler.removeMessages(UPDATE_TIME); if(mPlayer != null){ mPlayer?.stop() mPlayer?.release() } mPlayer = null; } fun onStop(){ mPlayer?.let { it.stop() } } fun onStart(){ mPlayer?.let { TagUtils.d("是否播放:${it.isPlaying}") if(!it.isPlaying){ it.prepareAsync() it.start() } } } override fun onClick(v: View) { when (v.id) { R.id.tv_backward -> backWard() R.id.tv_forward -> forWard() R.id.playOrPause -> play() //R.id.root_rl -> showControl() } } private fun updateTime() { startTime?.setText(formatLongToTimeStr(mPlayer!!.currentPosition)) mSeekBar!!.progress = mPlayer!!.currentPosition } private fun hideControl() { isShow = false mHandler.removeMessages(UPDATE_TIME) controlLl!!.animate().setDuration(300).translationY(controlLl!!.height.toFloat()) } private fun showControl() { if (isShow) { play() } isShow = true mHandler.removeMessages(HIDE_CONTROL) mHandler.sendEmptyMessage(UPDATE_TIME) mHandler.sendEmptyMessageDelayed( HIDE_CONTROL, 5000 ) controlLl!!.animate().setDuration(300).translationY(0f) } private fun forWard() { if (mPlayer != null) { val position = mPlayer!!.getCurrentPosition() mPlayer?.seekTo(position + 10000) } } fun backWard() { if (mPlayer != null) { var position = mPlayer!!.getCurrentPosition() if (position > 10000) { position -= 10000 } else { position = 0 } mPlayer?.seekTo(position) } } fun initData(filePath : String, isAutoPlay: Boolean) { path = filePath this.isAutoPlay = isAutoPlay processVideo() } //聊天页面下载小视频完成,自动播放 fun processChatDownload(){ videoSuf?.let { surfaceCreated(it.holder) } } private fun initEvent() { playOrPauseIv!!.setOnClickListener(this) //rootViewRl!!.setOnClickListener(this) forwardButton!!.setOnClickListener(this) backwardButton!!.setOnClickListener(this) mSeekBar!!.setOnSeekBarChangeListener(this) } private fun initSurfaceView() { TagUtils.d("initSurfaceView") videoSuf = findViewById<View>(R.id.surfaceView) as SurfaceView videoSuf?.let { it.setZOrderOnTop(false) it.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) it.getHolder().addCallback(this) TagUtils.d("initSurfaceView addCallback ") } } private fun initPlayer() { mPlayer = MediaPlayer() mPlayer?.let { it.setOnCompletionListener(this) it.setOnErrorListener(this) it.setOnInfoListener(this) it.setOnPreparedListener(this) it.setOnSeekCompleteListener(this) it.setOnVideoSizeChangedListener(this) try { TagUtils.d("文件是否存在:" + File(path).exists() + " , path = " + path) it.setDataSource(path) } catch (e: Exception) { e.printStackTrace() } } } //获取视频角度等信息 fun getVideoRoration(mUri: String?): Int { val roration = 0 val mmr = MediaMetadataRetriever() try { if (mUri != null) { var headers: HashMap<String, String> = HashMap<String, String>() headers["User-Agent"] = "Mozilla/5.0 (Linux; U; Android 4.4.2; zh-CN; MW-KW-001 Build/JRO03C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/1.0.0.001 U4/0.8.0 Mobile Safari/533.1" if (mUri.startsWith("http")) { mmr.setDataSource(mUri, headers) } else { mmr.setDataSource(mUri) } } val duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) //时长(毫秒) val width = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) //宽 val height = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) //高 val rotationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) // 视频旋转方向 TagUtils.d("视频信息:duration = $duration , width = $width , height = $height , rotationStr = $rotationStr") if (!TextUtils.isEmpty(rotationStr)) { return rotationStr!!.toInt() } TagUtils.d("rotation$rotationStr") } catch (ex: Exception) { TagUtils.d("MediaMetadataRetriever exception $ex") ex.printStackTrace() } finally { mmr.release() } return roration } private fun initViews() { view?.let { playOrPauseIv = it.findViewById(R.id.playOrPause) startTime = it.findViewById(R.id.tv_start_time) endTime = it.findViewById(R.id.tv_end_time) secondTime = it.findViewById(R.id.svideo_second) mSeekBar = it.findViewById(R.id.tv_progess) rootViewRl = it.findViewById(R.id.root_rl) controlLl = it.findViewById(R.id.control_ll) forwardButton = it.findViewById(R.id.tv_forward) backwardButton = it.findViewById(R.id.tv_backward) svideoThumbnail = it.findViewById(R.id.svideo_thumbnail) chatCircleProgress = it.findViewById(R.id.svideo_circle_progress) } } //设置圆形进度条 fun setCircleProgress(current:Int){ chatCircleProgress?.setCurrent(current) } fun showCircleProgress(){ chatCircleProgress?.visibility = View.VISIBLE } fun hideCircleProgress(){ chatCircleProgress?.visibility = View.GONE } /** * 显示缩略图 */ fun setThumbnail(thumbnailPath : String){ svideoThumbnail?.setImageBitmap(BitmapFactory.decodeFile(thumbnailPath)) } //本地小视频缩略图 fun setThumbnail(bitmap: Bitmap?){ svideoThumbnail?.setImageBitmap(bitmap) } //服务器小视频缩略图 fun setThumbnailServer(videoUrl : String){ svideoThumbnail?.let { GlideUtils.load(it, videoUrl) } } override fun surfaceCreated(holder: SurfaceHolder) { TagUtils.d("初始化surfaceCreated ") mPlayer!!.setDisplay(holder) mPlayer!!.prepareAsync() } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} override fun surfaceDestroyed(holder: SurfaceHolder) {} override fun onPrepared(mp: MediaPlayer) { startTime?.setText(formatLongToTimeStr(mp.currentPosition)) endTime?.setText(formatLongToTimeStr(mp.duration)) mSeekBar?.max = mp.duration mSeekBar?.progress = mp.currentPosition //TagUtils.d("初始化完成。。") if(isAutoPlay){ play() } } //显示多少秒 fun setSecond(second:Int){ secondTime?.visibility = View.VISIBLE secondTime?.setText("${second}秒") } override fun onCompletion(mp: MediaPlayer?) { //TagUtils.d("播放完成。。") playOrPauseIv?.visibility = View.VISIBLE playOrPauseIv!!.setImageResource(android.R.drawable.ic_media_play) } override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { return false } override fun onInfo(mp: MediaPlayer?, what: Int, extra: Int): Boolean { return false } fun play() { //TagUtils.d("开始播放:${mPlayer}, ${path}") if (mPlayer == null) { return } svideoThumbnail?.visibility = View.GONE videoSuf?.visibility = View.VISIBLE hideCircleProgress() if (mPlayer?.isPlaying()!!) { mPlayer?.pause() mHandler.removeMessages(UPDATE_TIME) mHandler.removeMessages(HIDE_CONTROL) playOrPauseIv!!.visibility = VISIBLE playOrPauseIv!!.setImageResource(android.R.drawable.ic_media_play) } else { mPlayer?.start() mHandler.sendEmptyMessageDelayed( UPDATE_TIME, 500 ) mHandler.sendEmptyMessageDelayed( HIDE_CONTROL, 5000 ) playOrPauseIv!!.visibility = INVISIBLE playOrPauseIv!!.setImageResource(android.R.drawable.ic_media_pause) } } override fun onSeekComplete(mp: MediaPlayer?) {} override fun onVideoSizeChanged(mp: MediaPlayer?, width: Int, height: Int) {} override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { if (mPlayer != null && fromUser) { mPlayer?.seekTo(progress) } } override fun onStartTrackingTouch(seekBar: SeekBar?) {} override fun onStopTrackingTouch(seekBar: SeekBar?) {} fun formatLongToTimeStr(millis: Int): String { return String.format( "%02d:%02d:%02d", millis / (1000 * 60 * 60), millis / (1000 * 60) % 60, millis / 1000 % 60 ) } }
//打开相册选择小视频 ImageSelector.builder() .useCamera(false) // 设置是否使用拍照 .setSingle(true) //设置是否单选 .canPreview(true) //是否点击放大图片查看,,默认为true .start(this,REQUEST_PICTURE_CODE)
//选择小视频后返回的文件地址 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_PICTURE_CODE && data != null) { val images = data.getStringArrayListExtra(ImageSelector.SELECT_RESULT) if(images != null && images.size > 0) { AddFileListener.sendFile(ChatBean.CONTENT_TYPE_IMG, images[0], toUserId,0) } } }
/** * 发送图片、小视频、语音 * @param fileType Int * @param picPath String * @param toUserID String */ fun sendFile(fileType: Int, filePath : String, toUserId : String, time: Int) { CoroutineScope(Dispatchers.IO).launch { //语音、小视频多少秒 val toId: String = BaseUtils.getChatId(toUserId) + "/Smack" var resultFilePath = "" if(TextUtils.isEmpty(filePath)){ return@launch } if(!File(filePath).exists()){ return@launch } var second = time if(fileType == ChatBean.CONTENT_TYPE_VOICE){ second = time } else if(fileType == ChatBean.CONTENT_TYPE_VIDEO){ second = CommonUtils.Media.getMediaTime(filePath, fileType) } //先刷新页面,再慢慢上传文件 var chatBean = processSendChatBean(toUserId, fileType, filePath, second) if(fileType == ChatBean.CONTENT_TYPE_IMG){ //图片 //压缩图片后路径 var resultPicPath = UploadFileUtils.getCompressFile(filePath) resultFilePath = resultPicPath } else if(fileType == ChatBean.CONTENT_TYPE_VOICE){ //语音 resultFilePath = filePath TagUtils.d("语音时间:${second}") } else if(fileType == ChatBean.CONTENT_TYPE_VIDEO){ //小视频 var videoFilePath = UploadFileUtils.getVideoCompressFile() //TagUtils.d("开始发送小视频压缩前文件2:${videoFilePath}") var compressResult = VideoController.getInstance().convertVideo(filePath, videoFilePath, VideoController.COMPRESS_QUALITY_LOW, object : VideoController.CompressProgressListener { override fun onProgress(percent: Float) { //TagUtils.d("压缩小视频进度:${percent}") } }) TagUtils.d("小视频时间:${second}") resultFilePath = videoFilePath } TagUtils.d("上传文件:${resultFilePath}") val filetosend = File(resultFilePath) if (!filetosend.exists()) { return@launch } try { var account : String = DataStoreUtils.getAccount() if(fileType == ChatBean.CONTENT_TYPE_IMG){ //图片 TagUtils.d("图片发送成功。") var chatBeanServer = UploadFileUtils.uploadChatImages(account, toUserId, resultFilePath,0) if(chatBeanServer != null){ chatBeanServer.imgPath = chatBeanServer.imgPath chatBean = chatBeanServer } } else if(fileType == ChatBean.CONTENT_TYPE_VOICE){ //录音完成,要转码,等待0.2秒再发送 delay(100) //语音 var chatBeanServer = UploadFileUtils.uploadChatVoice(account, toUserId, resultFilePath, second) if(chatBeanServer != null){ chatBeanServer.voiceLocal = resultFilePath chatBean = chatBeanServer } } else if(fileType == ChatBean.CONTENT_TYPE_VIDEO){ //小视频 var chatBeanServer = UploadFileUtils.uploadChatVideo(account, toUserId, resultFilePath, second) if(chatBeanServer != null){ chatBeanServer.videoLocal = resultFilePath chatBean = chatBeanServer } } chatBean?.let { ChatRepository.updateChat(it) } var baseSystemBoolean = BaseSystemRepository.getBaseSystemSync(WcApp.getContext().packageName) if(baseSystemBoolean != null && baseSystemBoolean.sync == CommonUtils.Sync.SERVER_SYNC){ //同步不需要使用transfer上传文件,因为transfer上传太慢了, 直接上传到服务器,然后发送普通的消息 if(fileType == ChatBean.CONTENT_TYPE_VOICE){ var content = CommonUtils.Chat.VOICE_MARK + chatBean.voice ChatManagerUtils.getInstance().sendMessage(toUserId, content) } else if(fileType == ChatBean.CONTENT_TYPE_VIDEO){ var content = CommonUtils.Chat.VIDEO_MARK + chatBean.video ChatManagerUtils.getInstance().sendMessage(toUserId, content) } else if(fileType == ChatBean.CONTENT_TYPE_IMG){ var content = CommonUtils.Chat.IMAGE_MARK + chatBean.imgPath ChatManagerUtils.getInstance().sendMessage(toUserId, content) } return@launch } val fileTransferManager = getFileTransferManager() val transfer = fileTransferManager.createOutgoingFileTransfer(toId) // 创建一个输出文件传输对象 //对方用户在线才需要上传文件 if(XmppConnectionManager.getInstance().isOnLine(toUserId)){ TagUtils.d("${toUserId} 在线 开始上传。") transfer.sendFile(filetosend,"recv img") while (!transfer.isDone) { if (transfer.status == FileTransfer.Status.error) { TagUtils.d("聊天文件上传错误 , ERROR!!! " + transfer.error) } else { TagUtils.d("聊天文件上传 " + transfer.status +" , " + transfer.progress) } Thread.sleep(20) } } else { TagUtils.d("toUserId 不在线") } if (transfer.isDone) { //上传完成 } } catch (e1: XMPPException) { e1.printStackTrace() } } }
/** * Author : wangning * Email : maoning20080809@163.com * Date : 2022/5/31 16:31 * Description : 处理聊天信息发来的信息 */ class ChatManagerListener : ChatManagerListener { override fun chatCreated(chat: Chat, createdLocally: Boolean) { TagUtils.d("消息监听回调:chat = ${chat} , createdLocally = ${createdLocally}") if(!createdLocally){ chat.addMessageListener { chat, message -> TagUtils.d("获取好友发来的信息 ${message.from} , ${message.to}, ${message.body}") var content = message.getBody() if(!TextUtils.isEmpty(content) && content.length > 0){ var fromUser = BaseUtils.getChatAccountFrom(message.from) var toUser = BaseUtils.getChatAccount(message.to) var userType = ChatBean.USER_TYPE_OTHER if(content.startsWith(CommonUtils.Chat.LOCATION_MARK)){ //发送定位 //去掉location###标志 var remarkContent = CommonUtils.Chat.getLocation(content) //使用逗号分隔符,分别读取经纬度 var contents = remarkContent.split(",") var latitude = contents[0].toDouble() var longitude = contents[1].toDouble() var chatBean = CommonUtils.Chat.getChatBean(fromUser, toUser, userType, content, ChatBean.CONTENT_TYPE_LOCATION, "", latitude, longitude) ChatRepository.insertChat(chatBean) chatBean.isReceive = true EventBus.getDefault().post(chatBean) } else if(content.startsWith(CommonUtils.Chat.REDPACKET_MARK)){ //发送红包, 去掉redpacket###写入数据库 content = CommonUtils.Chat.getRedpacket(content).toString() var chatBean = CommonUtils.Chat.getChatBean(fromUser, toUser, userType, content, ChatBean.CONTENT_TYPE_REDPACKET, "",0.0, 0.0) ChatRepository.insertChat(chatBean) chatBean.isReceive = true EventBus.getDefault().post(chatBean) } else if(content.startsWith(CommonUtils.Chat.VOICE_MARK)){ //发送语音, 去掉voice###写入数据库 content = CommonUtils.Chat.getMedia(content, CommonUtils.Chat.VOICE_MARK) var chatBean = CommonUtils.Chat.getChatBeanVoiceServer(fromUser, toUser, userType,ChatBean.CONTENT_TYPE_VOICE, content,0) chatBean.isReceive = true processDownload(chatBean) } else if(content.startsWith(CommonUtils.Chat.VIDEO_MARK)){ //发送小视频, 去掉video###写入数据库 content = CommonUtils.Chat.getMedia(content, CommonUtils.Chat.VIDEO_MARK) var chatBean = CommonUtils.Chat.getChatBeanVideoServer(fromUser, toUser, userType,ChatBean.CONTENT_TYPE_VIDEO, content,0) chatBean.isReceive = true processDownload(chatBean) } else if(content.startsWith(CommonUtils.Chat.IMAGE_MARK)){ //发送图片, 去掉image###写入数据库 content = CommonUtils.Chat.getMedia(content, CommonUtils.Chat.IMAGE_MARK) var chatBean = CommonUtils.Chat.getChatBeanImageServer(fromUser, toUser, userType,ChatBean.CONTENT_TYPE_IMG, content,0) chatBean.isReceive = true processDownload(chatBean) } else if(content.startsWith(CommonUtils.Chat.TRANSFER_MARK)){ //发送转账, 去掉transfer###写入数据库 content = CommonUtils.Chat.getTransfer(content).toString() var chatBean = CommonUtils.Chat.getChatBean(fromUser, toUser, userType, content, ChatBean.CONTENT_TYPE_TRANSFER, "",0.0, 0.0) ChatRepository.insertChat(chatBean) chatBean.isReceive = true EventBus.getDefault().post(chatBean) } else if(content.startsWith(CommonUtils.QRCommon.QR_RECEIVE_CODE)){ //向个人发送收款 var balance = content.substring(CommonUtils.QRCommon.QR_RECEIVE_CODE.length, content.length) TagUtils.d("MyChatManagerListener 向个人发送收款金额: ${fromUser} , ${toUser}, ${balance}") updateBalanceServer(fromUser, toUser, CommonUtils.User.OPERATOR_PLUS, balance.toFloat()) } else if(content.startsWith(CommonUtils.QRCommon.QR_PAYMENT_CODE)){ //向商家付款 var balance = content.substring(CommonUtils.QRCommon.QR_RECEIVE_CODE.length, content.length) TagUtils.d("MyChatManagerListener 向商家付款金额: ${fromUser} , ${toUser}, ${balance}") updateBalanceServer(fromUser, toUser, CommonUtils.User.OPERATOR_MINUS, balance.toFloat()) } else { var chatBean = CommonUtils.Chat.getChatBean(fromUser, toUser, userType, content, ChatBean.CONTENT_TYPE_TEXT, "",0.0, 0.0) ChatRepository.insertChat(chatBean) chatBean.isReceive = true EventBus.getDefault().post(chatBean) } ChatNotificationUtils.sendNotification(fromUser) } } } } /** * 下载图片、语音、小视频 */ private fun processDownload(chatBean : ChatBean){ var fileName = "" if(chatBean.contentType == ChatBean.CONTENT_TYPE_VOICE){ fileName = chatBean.voice } else if(chatBean.contentType == ChatBean.CONTENT_TYPE_VIDEO){ fileName = chatBean.video } else if(chatBean.contentType == ChatBean.CONTENT_TYPE_IMG){ fileName = chatBean.imgPath } else { return } var videoUrl = CommonUtils.Moments.getReallyImageUrl(fileName) var videoFile = FileUtils.getBaseFile(fileName) TagUtils.d("下载多媒体Url :${videoUrl} ") TagUtils.d("下载多媒体File :${videoFile} ") VideoDownloadManager.downloadFast(videoUrl, videoFile, object : VideoDownloadInter { override fun onDone(filePath: String) { TagUtils.d("小视频多媒体完成:${filePath}") if(chatBean.contentType == ChatBean.CONTENT_TYPE_VOICE){ var second = CommonUtils.Media.getMediaTime(filePath, chatBean.contentType) chatBean.second = second chatBean.voiceLocal = filePath } else if(chatBean.contentType == ChatBean.CONTENT_TYPE_VIDEO){ chatBean.videoLocal = filePath var second = CommonUtils.Media.getMediaTime(filePath, chatBean.contentType) chatBean.second = second } else if(chatBean.contentType == ChatBean.CONTENT_TYPE_IMG){ chatBean.imgPathLocal = filePath } ChatRepository.insertChat(chatBean) EventBus.getDefault().post(chatBean) } override fun onError() { } override fun onProgress(process: Int) { } }) }
/** * 聊天小视频上传到服务器, 因为发送之前已经压缩,这里不需要再压缩了 */ fun uploadChatVideo(fromAccount: String, toAccount: String, filePath : String, second: Int) : ChatBean? { if(TextUtils.isEmpty(fromAccount)){ return null } if(TextUtils.isEmpty(toAccount)){ return null } if(TextUtils.isEmpty(filePath)){ return null } var client = OkHttpClient() var builder = MultipartBody.Builder() var mediaType : MediaType? = "application/json; charset=utf-8".toMediaTypeOrNull() var url = BaseUtils.BASE_URL +"chat?method=uploadChatVideo" TagUtils.d("uploadVideo url = ${url}") var file = File(filePath) builder.addFormDataPart("file", filePath, file.asRequestBody(mediaType)) builder.addFormDataPart("from_account", fromAccount) builder.addFormDataPart("to_account", toAccount) builder.addFormDataPart("content_type", ChatBean.CONTENT_TYPE_VIDEO.toString()) builder.addFormDataPart("user_type", ChatBean.USER_TYPE_ME.toString()) builder.addFormDataPart("message_id", UUID.randomUUID().toString()) builder.addFormDataPart("second", second.toString()) var requestBody = builder.build() var reqBuilder = Request.Builder() var request = reqBuilder .url(url) .post(requestBody) .build() var call = client.newCall(request) var response = call.execute() var result = response.body?.string() var baseResult = Gson().fromJson(result , BaseResult::class.java) if(baseResult.isSuccess){ var chatBeanGson = baseResult.data as String var chatBean = Gson().fromJson(chatBeanGson, ChatBean::class.java) return chatBean } else { return null } }