Files
forkmessager/client-mobile/chats/USAGE_EXAMPLES.md
Халимов Рустам 7c66e1c0c0 Кэш чатов
2026-04-20 00:25:40 +03:00

8.5 KiB
Raw Blame History

Примеры использования системы кэширования

1. Paging 3 в ViewModel

@HiltViewModel
class ChatViewModel @Inject constructor(
    private val repository: ChatRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    private val chatId: String = savedStateHandle["chatId"] ?: ""
    
    // Paging 3 поток для UI
    val messages: Flow<PagingData<Message>> = repository
        .getMessagesPaging(chatId)
        .cachedIn(viewModelScope)
    
    // Простой Flow для небольших чатов
    val messagesList: Flow<List<Message>> = repository
        .getMessagesFlow(chatId)
}

2. UI с Paging 3 (Jetpack Compose)

@Composable
fun ChatScreen(viewModel: ChatViewModel = hiltViewModel()) {
    val messages by viewModel.messages.collectAsLazyPagingItems()
    
    LazyColumn {
        items(
            count = messages.itemCount,
            key = messages.key
        ) { index ->
            val message = messages[index]
            message?.let {
                MessageItem(message = it)
            }
        }
        
        // Индикаторы загрузки
        when {
            messages.loadState.refresh is LoadState.Loading -> {
                item { LoadingIndicator() }
            }
            messages.loadState.append is LoadState.Loading -> {
                item { LoadingIndicator() }
            }
            messages.loadState.prepend is LoadState.Loading -> {
                item { LoadingIndicator() }
            }
        }
        
        // Обработка ошибок
        messages.loadState.append.let { loadState ->
            if (loadState is LoadState.Error) {
                item {
                    ErrorView(
                        message = loadState.error.message,
                        onRetry = { messages.retry() }
                    )
                }
            }
        }
    }
}

3. Отправка сообщения (Offline-first)

@HiltViewModel
class ChatViewModel @Inject constructor(
    private val repository: ChatRepository
) : ViewModel() {
    
    fun sendMessage(chatId: String, content: String) {
        viewModelScope.launch {
            try {
                // Сообщение сразу сохраняется локально и появляется в UI
                val message = repository.sendMessage(
                    chatId = chatId,
                    content = content,
                    type = "text"
                )
                
                // UI обновляется мгновенно через Flow
                Log.d("ChatViewModel", "Message saved locally: ${message.id}")
                
                // Синхронизация с сервером произойдёт в фоне
            } catch (e: Exception) {
                Log.e("ChatViewModel", "Failed to send message", e)
            }
        }
    }
}

4. Удаление сообщения

fun deleteMessage(messageId: String) {
    viewModelScope.launch {
        // Локальное удаление (сообщение скрывается из UI)
        repository.deleteLocalMessage(messageId)
        
        // WorkManager удалит сообщение на сервере в фоне
        // При получении подтверждения - сообщение удаляется из БД
    }
}

// Или немедленное удаление (если онлайн)
fun deleteMessageImmediately(messageId: String, forEveryone: Boolean) {
    viewModelScope.launch {
        repository.deleteMessage(messageId, forEveryone)
    }
}

5. Редактирование сообщения

fun editMessage(messageId: String, newContent: String) {
    viewModelScope.launch {
        // Локальное редактирование
        repository.editLocalMessage(messageId, newContent)
        
        // UI обновляется мгновенно
        // WorkManager отправит изменения на сервер в фоне
    }
}

6. Отслеживание статуса синхронизации

// Наблюдение за сообщениями, ожидающими синхронизации
fun observePendingMessages() {
    viewModelScope.launch {
        messageDao.getPendingSyncMessagesFlow().collect { messages ->
            if (messages.isNotEmpty()) {
                Log.d("Sync", "${messages.size} messages pending sync")
            }
        }
    }
}

// Проверка статуса конкретного сообщения
fun isMessageSynced(messageId: String): Boolean {
    return runBlocking {
        val message = messageDao.getMessageById(messageId)
        message?.syncStatus == SyncStatus.SYNCED
    }
}

7. Обработка ошибок синхронизации

fun retryFailedMessages() {
    viewModelScope.launch {
        val failedMessages = messageDao.getFailedSyncMessages()
        
        failedMessages.forEach { message ->
            when {
                message.isDeletedLocally -> {
                    // Повторить удаление
                    MessageSyncWorker.scheduleSync(context)
                }
                message.isEditedLocally -> {
                    // Повторить редактирование
                    MessageSyncWorker.scheduleSync(context)
                }
                else -> {
                    // Повторить отправку
                    MessageSyncWorker.scheduleSync(context)
                }
            }
        }
    }
}

8. Прочтение сообщений

fun markAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) {
    viewModelScope.launch {
        // Отправляем статус прочтения через SignalR
        repository.markMessagesAsRead(chatId, lastMessageId, lastReadSequenceId)
        
        // Локальная база обновляется автоматически
    }
}

9. Real-time обновления

SignalR события обрабатываются автоматически:

  • Новые сообщения появляются в UI мгновенно
  • Редактирования/удаления синхронизируются
  • Статусы прочтения обновляются
  • Реакции отображаются в реальном времени
// Обработчик SignalR уже интегрирован в ChatRepositoryImpl
// Дополнительные действия можно добавить в MessageSignalRHandler

10. Кэширование в ViewModel

@HiltViewModel
class ChatViewModel @Inject constructor(
    private val repository: ChatRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    private val chatId: String = savedStateHandle["chatId"] ?: ""
    
    // Кэшируем PagingData в scope ViewModel
    val messages: Flow<PagingData<Message>> = repository
        .getMessagesPaging(chatId)
        .cachedIn(viewModelScope)  // Важно для сохранения состояния
    
    // При повороте экрана пагинация сохраняется
}

Рекомендации

1. Выбор между Paging 3 и Flow

  • Paging 3 - для больших чатов (>100 сообщений)
  • Flow - для небольших чатов или когда нужна вся история сразу

2. Обработка офлайн-режима

// UI должен показывать статус сообщения
@Composable
fun MessageItem(message: Message) {
    val isPending = message.id.startsWith("local_")
    
    Row {
        Text(text = message.content)
        if (isPending) {
            CircularProgressIndicator(modifier = Modifier.size(12.dp))
        }
    }
}

3. Конфликты данных

  • Локальные изменения имеют приоритет во время отправки
  • Серверные данные перезаписывают локальные после SYNCED
  • SignalR события всегда применяются к актуальным данным

4. Производительность

  • Используйте cachedIn(viewModelScope) для PagingData
  • Избегайте частых вызовов getMessages() из сети
  • Позволяйте WorkManager управлять синхронизацией