UI and lifecycle
The SDK doesn't provide complete chat UI components. Whether you use Activities, Fragments, ViewModels or Compose, you subscribe to the same Flow state, lists and message handles. This page shows how to tie those subscriptions to the UI lifecycle, and which resources to release when a screen ends.
| You want to | See |
|---|---|
| Show the conversation list in an Activity / Fragment | Activity / Fragment |
| Subscriptions and read marking on the message screen | Message screen |
| Use it in Compose | Compose |
| Show names, group notices, recall notices | Display helpers |
| Know what to release when a screen ends | Resource ownership |
Activity / Fragment
Add lifecycle-runtime-ktx and collect while the LifecycleOwner is STARTED. In Fragments use viewLifecycleOwner, so a destroyed view never keeps updating an old UI.
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import com.deeprespond.im.*
fun observeConversations(owner: LifecycleOwner, im: DRClient) {
owner.lifecycleScope.launch {
owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
im.conversations.listFlow().collect { snapshot ->
println(snapshot.items.size) // 更新会话列表。
}
}
}
}listFlow()creates the list when collection starts and releases it when collection stops. If you need to callloadMoreyourself, create aLiveListinstead and close it withtry/finally.- To collect several
StateFlows at once,launcheach one separately insiderepeatOnLifecycle: the firstcollectnever ends, so a second one written after it would never run.
Message screen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import com.deeprespond.im.*
fun observeMessages(owner: LifecycleOwner, im: DRClient, conversationKey: String) {
owner.lifecycleScope.launch {
owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
val list = im.messages.open(conversationKey)
try {
list.setViewing(atBottom = true, visible = true)
list.snapshot.collect { snapshot -> println(snapshot.items.size) }
} finally {
list.setViewing(visible = false)
list.close()
}
}
}
}The example fixes "at the newest message" to true; a real screen updates atBottom as the user scrolls and sets visible to false when the screen isn't shown. Cancelling the state collection doesn't leave a chat room or hang up a call; handle those separately according to your navigation.
Compose
- Add the lifecycle Compose integration and read
StateFlows withcollectAsStateWithLifecycle. listFlow()can be collected directly. AMessageListyou open yourself is created inrememberkeyed by the conversation key, closed inDisposableEffect, withvisibleset according to the lifecycle. Don't callmessages.openon every recomposition.- Stable list keys:
conversationKeyfor conversations;clientMsgIdfor messages, keeping the same view once the message is sent and gets its real sequence number;usernamefor group members and friends. - Snapshots are read-only. Change data by calling SDK methods, not by modifying
rawor the snapshot lists.
Display helpers
import com.deeprespond.im.render.*
val title = conversationTitle(im, conversation)
val sender = displayName(im, "bob", groupId)
val summary = summarize(im, message, groupId)
println("$title / $sender / $summary")displayNametakes the first non-empty of friend remark, group nickname, nickname and username.renderTipproduces group notice text,renderRecallrecall notices andrenderCallRecordcall record text;DRLocale.zhCNprovides the Chinese strings, anddescribeErrorturns errors into message text. These functions only handle display; what's allowed is still decided by the API.- For images use
DRImageandaddDRImage; see Coil image loading. For video useDRCallVideoView, andunbindit when the screen ends. - Full-size images, downloads and file sharing are handled by your app's own screens, storage and FileProvider.
Resource ownership
| Resource | When the screen or task ends |
|---|---|
LiveList, MessageList | close() |
presence.subscribe | Close the returned AutoCloseable |
Event and state Flows | Cancel the coroutine |
The handle from keepForeground() | close() |
| A chat room you entered | room.leave() when leaving |
| Uploads, downloads | Call task.cancel() to really cancel; cancelling the wait alone doesn't stop the task |
| Video views | unbind() |
| The whole client | destroy() only when switching apps or shutting down completely |
Channel video and cleanup
For standalone channel video, use DRChannelVideoView from im-call, binding only the ChannelTracks given in tracks; it supports FIT / FILL and mirroring. When the screen exits: stop observing the StateFlows, unbind the view, and call handle.leave() in a coroutine that can run to completion. Just destroying the View doesn't leave the channel. See Channel video.
