Skip to content

Commit 56fe117

Browse files
committed
chore(fc): extract paging operator classes into own files
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 4ed1332 commit 56fe117

File tree

3 files changed

+145
-120
lines changed

3 files changed

+145
-120
lines changed

services/flipchat/sdk/src/main/kotlin/xyz/flipchat/chat/RoomController.kt

Lines changed: 3 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ package xyz.flipchat.chat
33
import android.annotation.SuppressLint
44
import androidx.core.app.NotificationManagerCompat
55
import androidx.paging.ExperimentalPagingApi
6-
import androidx.paging.LoadType
76
import androidx.paging.Pager
87
import androidx.paging.PagingConfig
98
import androidx.paging.PagingSource
109
import androidx.paging.PagingState
11-
import androidx.paging.RemoteMediator
1210
import androidx.room.paging.util.ThreadSafeInvalidationObserver
1311
import androidx.room.withTransaction
1412
import com.getcode.model.ID
1513
import com.getcode.model.KinAmount
16-
import com.getcode.model.chat.MessageContent
1714
import com.getcode.model.chat.MessageStatus
1815
import com.getcode.model.uuid
1916
import com.getcode.services.model.chat.OutgoingMessageContent
@@ -24,6 +21,8 @@ import kotlinx.coroutines.Dispatchers
2421
import kotlinx.coroutines.flow.Flow
2522
import kotlinx.coroutines.launch
2623
import kotlinx.coroutines.withContext
24+
import xyz.flipchat.chat.paging.MessagingPagingSource
25+
import xyz.flipchat.chat.paging.MessagingRemoteMediator
2726
import xyz.flipchat.internal.db.FcAppDatabase
2827
import xyz.flipchat.notifications.getRoomNotifications
2928
import xyz.flipchat.services.data.ChatIdentifier
@@ -32,7 +31,6 @@ import xyz.flipchat.services.domain.model.chat.ConversationMember
3231
import xyz.flipchat.services.domain.model.chat.ConversationMessageWithMemberAndContent
3332
import xyz.flipchat.services.domain.model.chat.ConversationWithMembersAndLastPointers
3433
import xyz.flipchat.services.domain.model.chat.InflatedConversationMessage
35-
import xyz.flipchat.services.domain.model.query.QueryOptions
3634
import xyz.flipchat.services.internal.data.mapper.ConversationMemberMapper
3735
import xyz.flipchat.services.internal.network.repository.chat.ChatRepository
3836
import xyz.flipchat.services.internal.network.repository.messaging.MessagingRepository
@@ -199,7 +197,7 @@ class RoomController @Inject constructor(
199197
fun messages(conversationId: ID): Pager<Int, InflatedConversationMessage> =
200198
Pager(
201199
config = pagingConfig,
202-
remoteMediator = MessagesRemoteMediator(
200+
remoteMediator = MessagingRemoteMediator(
203201
chatId = conversationId,
204202
repository = messagingRepository,
205203
conversationMessageMapper = conversationMessageMapper,
@@ -334,118 +332,3 @@ class RoomController @Inject constructor(
334332
}
335333
}
336334

337-
private class MessagingPagingSource(
338-
private val chatId: ID,
339-
private val userId: () -> ID?,
340-
private val db: FcAppDatabase
341-
) : PagingSource<Int, InflatedConversationMessage>() {
342-
343-
@SuppressLint("RestrictedApi")
344-
private val observer =
345-
ThreadSafeInvalidationObserver(arrayOf("conversations", "messages", "members")) {
346-
invalidate()
347-
}
348-
349-
override fun getRefreshKey(state: PagingState<Int, InflatedConversationMessage>): Int? {
350-
val anchorPos = state.anchorPosition ?: return null
351-
val anchorItem = state.closestItemToPosition(anchorPos) ?: return null
352-
// The anchor item *knows* which page it was loaded from:
353-
return anchorItem.pageIndex
354-
}
355-
356-
@SuppressLint("RestrictedApi")
357-
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, InflatedConversationMessage> {
358-
observer.registerIfNecessary(db)
359-
val currentPage = params.key ?: 0
360-
val pageSize = params.loadSize
361-
val offset = currentPage * pageSize
362-
363-
return withContext(Dispatchers.Default) {
364-
try {
365-
val messages =
366-
db.conversationMessageDao()
367-
.getPagedMessagesWithDetails(chatId, pageSize, offset, userId())
368-
.map { it.copy(pageIndex = currentPage) }
369-
370-
val prevKey = if (currentPage > 0 && messages.isNotEmpty()) currentPage - 1 else null
371-
val nextKey = if (messages.size < pageSize) null else currentPage + 1
372-
373-
LoadResult.Page(
374-
data = messages,
375-
prevKey = prevKey,
376-
nextKey = nextKey,
377-
)
378-
} catch (e: Exception) {
379-
LoadResult.Error(e)
380-
}
381-
}
382-
}
383-
}
384-
385-
@OptIn(ExperimentalPagingApi::class)
386-
private class MessagesRemoteMediator(
387-
private val chatId: ID,
388-
private val repository: MessagingRepository,
389-
private val conversationMessageMapper: ConversationMessageMapper,
390-
private val userId: () -> ID?
391-
) : RemoteMediator<Int, InflatedConversationMessage>() {
392-
393-
private val db = FcAppDatabase.requireInstance()
394-
395-
override suspend fun initialize(): InitializeAction {
396-
return InitializeAction.SKIP_INITIAL_REFRESH
397-
}
398-
399-
override suspend fun load(
400-
loadType: LoadType,
401-
state: PagingState<Int, InflatedConversationMessage>
402-
): MediatorResult {
403-
return try {
404-
val loadKey = when (loadType) {
405-
LoadType.REFRESH -> {
406-
null
407-
}
408-
409-
LoadType.PREPEND -> {
410-
return MediatorResult.Success(true) // Don't load newer messages
411-
}
412-
413-
LoadType.APPEND -> {
414-
// Get the last item from our data
415-
state.lastItemOrNull()?.message?.id
416-
}
417-
}
418-
419-
val limit = state.config.pageSize
420-
421-
val query = QueryOptions(
422-
limit = limit,
423-
token = loadKey,
424-
descending = true
425-
)
426-
427-
val response = withContext(Dispatchers.IO) { repository.getMessages(chatId, query) }
428-
val messages = response.getOrNull().orEmpty()
429-
430-
val conversationMessages =
431-
messages.map { conversationMessageMapper.map(chatId to it) }
432-
433-
if (conversationMessages.isEmpty()) {
434-
return MediatorResult.Success(true)
435-
}
436-
437-
withContext(Dispatchers.IO) {
438-
if (loadType == LoadType.REFRESH) {
439-
db.conversationMessageDao().clearMessagesForChat(chatId)
440-
}
441-
442-
db.conversationMessageDao()
443-
.upsertMessages(messages = conversationMessages, selfID = userId())
444-
}
445-
446-
MediatorResult.Success(endOfPaginationReached = messages.size < limit)
447-
} catch (e: Exception) {
448-
MediatorResult.Error(e)
449-
}
450-
}
451-
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package xyz.flipchat.chat.paging
2+
3+
import android.annotation.SuppressLint
4+
import androidx.paging.PagingSource
5+
import androidx.paging.PagingState
6+
import androidx.room.paging.util.ThreadSafeInvalidationObserver
7+
import com.getcode.model.ID
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.withContext
10+
import xyz.flipchat.internal.db.FcAppDatabase
11+
import xyz.flipchat.services.domain.model.chat.InflatedConversationMessage
12+
13+
internal class MessagingPagingSource(
14+
private val chatId: ID,
15+
private val userId: () -> ID?,
16+
private val db: FcAppDatabase
17+
) : PagingSource<Int, InflatedConversationMessage>() {
18+
19+
@SuppressLint("RestrictedApi")
20+
private val observer =
21+
ThreadSafeInvalidationObserver(arrayOf("conversations", "messages", "members")) {
22+
invalidate()
23+
}
24+
25+
override fun getRefreshKey(state: PagingState<Int, InflatedConversationMessage>): Int? {
26+
val anchorPos = state.anchorPosition ?: return null
27+
val anchorItem = state.closestItemToPosition(anchorPos) ?: return null
28+
// The anchor item *knows* which page it was loaded from:
29+
return anchorItem.pageIndex
30+
}
31+
32+
@SuppressLint("RestrictedApi")
33+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, InflatedConversationMessage> {
34+
observer.registerIfNecessary(db)
35+
val currentPage = params.key ?: 0
36+
val pageSize = params.loadSize
37+
val offset = currentPage * pageSize
38+
39+
return withContext(Dispatchers.Default) {
40+
try {
41+
val messages =
42+
db.conversationMessageDao()
43+
.getPagedMessagesWithDetails(chatId, pageSize, offset, userId())
44+
.map { it.copy(pageIndex = currentPage) }
45+
46+
val prevKey = if (currentPage > 0 && messages.isNotEmpty()) currentPage - 1 else null
47+
val nextKey = if (messages.size < pageSize) null else currentPage + 1
48+
49+
LoadResult.Page(
50+
data = messages,
51+
prevKey = prevKey,
52+
nextKey = nextKey,
53+
)
54+
} catch (e: Exception) {
55+
LoadResult.Error(e)
56+
}
57+
}
58+
}
59+
}
60+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package xyz.flipchat.chat.paging
2+
3+
import androidx.paging.ExperimentalPagingApi
4+
import androidx.paging.LoadType
5+
import androidx.paging.PagingState
6+
import androidx.paging.RemoteMediator
7+
import com.getcode.model.ID
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.withContext
10+
import xyz.flipchat.internal.db.FcAppDatabase
11+
import xyz.flipchat.services.domain.mapper.ConversationMessageMapper
12+
import xyz.flipchat.services.domain.model.chat.InflatedConversationMessage
13+
import xyz.flipchat.services.domain.model.query.QueryOptions
14+
import xyz.flipchat.services.internal.network.repository.messaging.MessagingRepository
15+
16+
@OptIn(ExperimentalPagingApi::class)
17+
internal class MessagingRemoteMediator(
18+
private val chatId: ID,
19+
private val repository: MessagingRepository,
20+
private val conversationMessageMapper: ConversationMessageMapper,
21+
private val userId: () -> ID?
22+
) : RemoteMediator<Int, InflatedConversationMessage>() {
23+
24+
private val db = FcAppDatabase.requireInstance()
25+
26+
override suspend fun initialize(): InitializeAction {
27+
return InitializeAction.SKIP_INITIAL_REFRESH
28+
}
29+
30+
override suspend fun load(
31+
loadType: LoadType,
32+
state: PagingState<Int, InflatedConversationMessage>
33+
): MediatorResult {
34+
return try {
35+
val loadKey = when (loadType) {
36+
LoadType.REFRESH -> {
37+
null
38+
}
39+
40+
LoadType.PREPEND -> {
41+
return MediatorResult.Success(true) // Don't load newer messages
42+
}
43+
44+
LoadType.APPEND -> {
45+
// Get the last item from our data
46+
state.lastItemOrNull()?.message?.id
47+
}
48+
}
49+
50+
val limit = state.config.pageSize
51+
52+
val query = QueryOptions(
53+
limit = limit,
54+
token = loadKey,
55+
descending = true
56+
)
57+
58+
val response = withContext(Dispatchers.IO) { repository.getMessages(chatId, query) }
59+
val messages = response.getOrNull().orEmpty()
60+
61+
val conversationMessages =
62+
messages.map { conversationMessageMapper.map(chatId to it) }
63+
64+
if (conversationMessages.isEmpty()) {
65+
return MediatorResult.Success(true)
66+
}
67+
68+
withContext(Dispatchers.IO) {
69+
if (loadType == LoadType.REFRESH) {
70+
db.conversationMessageDao().clearMessagesForChat(chatId)
71+
}
72+
73+
db.conversationMessageDao()
74+
.upsertMessages(messages = conversationMessages, selfID = userId())
75+
}
76+
77+
MediatorResult.Success(endOfPaginationReached = messages.size < limit)
78+
} catch (e: Exception) {
79+
MediatorResult.Error(e)
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)