本文系统地解析了环信 IM Demo 中用户头像和昵称的完整实现逻辑,涵盖了从UI显示到数据存储和同步的完整流程。接下来我们逐一拆解每个环节的实现细节,带您全面了解环信Demo中的用户信息管理机制,以便您快速实现开发。
1. 展示层(UI层)
1.1 聊天界面如何获取用户信息
// DemoDataModel.kt
fungetUser(userId: String?): DemoUser?{
if(userId.isNullOrEmpty())returnnull
if(contactList.containsKey(userId)){
return contactList[userId]
}
returngetUserDao().getUser(userId)
}
2. 拉取层(Repository层)
2.1 用户资料拉取调用链
// ProfileInfoViewModel.kt
funfetchUserInfoAttribute(userIds: List<String>, attributes: List<ChatUserInfoType>)=
flow {
emit(mRepository.getUserInfoAttribute(userIds, attributes))
}
// ProfileInfoRepository.kt
suspendfungetUserInfoAttribute(userIds: List<String>, attributes: List<ChatUserInfoType>): Map<String, ChatUserInfo>=
withContext(Dispatchers.IO){
ChatClient.getInstance().userInfoManager().fetUserInfo(userIds,attributes)
}
3. 存储层(本地数据库+缓存)
3.1 存入本地数据库和缓存
// DemoDataModel.kt
funinsertUser(user: ChatUIKitProfile, isInsertDb: Boolean =true){
if(isInsertDb){
getUserDao().insertUser(user.parseToDbBean())
}
contactList[user.id]= user.parseToDbBean()
}
4. UI刷新层
4.1 通知UI刷新
// DemoDataModel.kt
if(users.isNotEmpty()){
ChatUIKitClient.updateUsersInfo(users)
}
5. 用户主动修改头像/昵称
5.1 修改昵称
// ProfileInfoViewModel.kt
funupdateUserNickName(nickName:String)=
flow {
emit(mRepository.updateNickname(nickName))
}
// ProfileInfoRepository.kt
suspendfunupdateNickname(nickname: String)=
withContext(Dispatchers.IO){
ChatClient.getInstance().userInfoManager().updateOwnAttribute(ChatUserInfoType.NICKNAME, nickname)
}
5.2 修改头像
// ProfileInfoViewModel.kt
funuploadAvatar(filePath: String?)=
flow {
emit(mRepository.uploadAvatar(filePath))
}.flatMapConcat{ result ->
ChatUIKitClient.getCurrentUser()?.let{
it.avatar = result
DemoHelper.getInstance().getDataModel().insertUser(it)
ChatUIKitClient.updateCurrentUser(it)
}
flow {
emit(mRepository.uploadAvatarToChatServer(result))
}
}
6. 本地数据结构
DemoUser(数据库表结构)
@Entity
dataclassDemoUser(
@PrimaryKeyval userId: String,
val name: String?,
val avatar: String?,
val remark: String?=null,
@ColumnInfo(name ="update_times")
var updateTimes: Int =0
)
DemoUserDao(数据库操作)
@Dao
interface DemoUserDao {@Insert(onConflict = OnConflictStrategy.REPLACE)funinsertUser(user: DemoUser)@Query("SELECT * FROM DemoUser WHERE userId = :userId")fungetUser(userId: String): DemoUser?@Query("SELECT * FROM DemoUser")fungetAll(): List<DemoUser>@Insert(onConflict = OnConflictStrategy.REPLACE)funinsertUsers(users: List<DemoUser>)@UpdatefunupdateUser(user: DemoUser)@Query("UPDATE DemoUser SET name = :name, avatar = :avatar, remark = :remark WHERE userId = :userId")funupdateUser(userId: String, name: String, avatar: String, remark: String)@Query("UPDATE DemoUser SET update_times = update_times + 1 WHERE userId IN (:userIds)")funupdateUsersTimes(userIds: List<String>)@Query("UPDATE DemoUser SET update_times = 0")funresetUsersTimes()@DeletefundeleteUser(user: DemoUser)@Query("DELETE FROM DemoUser WHERE userId = :userId")fundeleteUserById(userId: String)@Query("DELETE FROM DemoUser WHERE userId IN (:userIds)")fundeleteUsersByIds(userIds: List<String>)}
7. 用户资料同步机制
7.1 Profile同步
// ProfileInfoViewModel.kt
funsynchronizeProfile(isSyncFromServer:Boolean =false)= flow {emit(mRepository.synchronizeProfile(isSyncFromServer))}// ProfileInfoRepository.ktsuspendfunsynchronizeProfile(isSyncFromServer:Boolean):ChatUIKitProfile?=withContext(Dispatchers.IO){val currentProfile = ChatUIKitClient.getCurrentUser()?:ChatUIKitProfile(ChatClient.getInstance().currentUser)val user = DemoHelper.getInstance().getDataModel().getUser(currentProfile.id) suspendCoroutine { continuation ->if(user ==null|| isSyncFromServer){// 从服务器获取用户信息 currentProfile.let{ profile->val ids =mutableListOf(profile.id)val type =mutableListOf(ChatUserInfoType.NICKNAME,ChatUserInfoType.AVATAR_URL)// ... 获取用户信息逻辑}}else{// 使用本地用户信息 currentProfile.let{ it.name = user.name it.avatar = user.avatar ChatUIKitClient.updateUsersInfo(mutableListOf(it))} continuation.resume(currentProfile)}}}
7.2 缓存更新
// DemoDataModel.kt
funupdateUserCache(userId: String?){
if(userId.isNullOrEmpty()){
return
}
val user = contactList[userId]?.parse()?:return
ChatClient.getInstance().contactManager().fetchContactFromLocal(userId)?.remark?.let{ remark ->
user.remark = remark
}
ChatUIKitClient.updateUsersInfo(mutableListOf(user))
}
8. 完整调用链总结
UI展示 → DemoHelper.getInstance().getDataModel().getUser(userId) → 本地有则直接展示
本地无数据 → ProfileInfoViewModel.fetchUserInfoAttribute → ProfileInfoRepository.getUserInfoAttribute → 环信SDK拉取
拉取成功 → DemoDataModel.insertUser → ChatUIKitClient.updateUsersInfo → UI自动刷新
用户主动修改 → ProfileInfoViewModel.updateUserNickName/uploadAvatar → ProfileInfoRepository → 环信SDK → 本地更新 → UI刷新
用户资料同步 → ProfileInfoViewModel.synchronizeProfile → ProfileInfoRepository.synchronizeProfile → 服务器/本地数据 → 本地更新 → UI刷新
9. ChatContactCheckActivity获取头像昵称后的更新逻辑详解
9.1 拉取用户信息
lifecycleScope.launch{ user?.let{ user-> model.fetchUserInfoAttribute(listOf(user.userId),listOf(ChatUserInfoType.NICKNAME, ChatUserInfoType.AVATAR_URL)).catchChatException{...}.collect{ it[user.userId]?.parseToDbBean()?.let{u-> u.parse().apply{ remark = ChatClient.getInstance().contactManager().fetchContactFromLocal(id)?.remark ChatUIKitClient.updateUsersInfo(mutableListOf(this)) DemoHelper.getInstance().getDataModel().insertUser(this)}updateUserInfo()notifyUpdateRemarkEvent()}}}}
9.2 刷新UI
privatefunupdateUserInfo(){
DemoHelper.getInstance().getDataModel().getUser(user?.userId)?.let{
val ph = AppCompatResources.getDrawable(this, R.drawable.uikit_default_avatar)
val ep = AppCompatResources.getDrawable(this, R.drawable.uikit_default_avatar)
binding.ivAvatar.load(it.parse().avatar ?: ph){
placeholder(ph)
error(ep)
}
binding.tvName.text = it.name?.ifEmpty{ it.userId }?: it.userId
}
}
9.3 广播用户资料变更事件
privatefunnotifyUpdateRemarkEvent(){
DemoHelper.getInstance().getDataModel().updateUserCache(user?.userId)
ChatUIKitFlowBus.with<ChatUIKitEvent>(ChatUIKitEvent.EVENT.UPDATE + ChatUIKitEvent.TYPE.CONTACT + DemoConstant.EVENT_UPDATE_USER_SUFFIX)
.post(lifecycleScope,ChatUIKitEvent(DemoConstant.EVENT_UPDATE_USER_SUFFIX, ChatUIKitEvent.TYPE.CONTACT, user?.userId))
}
9.4 调用链总结
1.拉取用户资料(头像/昵称)→ 存本地 → 刷新UIKit缓存 → 刷新当前页面UI → 广播变更事件
2.其他界面监听到事件后,也会自动刷新对应用户的头像昵称


