# Примеры использования системы кэширования ## 1. Paging 3 в ViewModel ```kotlin @HiltViewModel class ChatViewModel @Inject constructor( private val repository: ChatRepository, savedStateHandle: SavedStateHandle ) : ViewModel() { private val chatId: String = savedStateHandle["chatId"] ?: "" // Paging 3 поток для UI val messages: Flow> = repository .getMessagesPaging(chatId) .cachedIn(viewModelScope) // Простой Flow для небольших чатов val messagesList: Flow> = repository .getMessagesFlow(chatId) } ``` ## 2. UI с Paging 3 (Jetpack Compose) ```kotlin @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) ```kotlin @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. Удаление сообщения ```kotlin fun deleteMessage(messageId: String) { viewModelScope.launch { // Локальное удаление (сообщение скрывается из UI) repository.deleteLocalMessage(messageId) // WorkManager удалит сообщение на сервере в фоне // При получении подтверждения - сообщение удаляется из БД } } // Или немедленное удаление (если онлайн) fun deleteMessageImmediately(messageId: String, forEveryone: Boolean) { viewModelScope.launch { repository.deleteMessage(messageId, forEveryone) } } ``` ## 5. Редактирование сообщения ```kotlin fun editMessage(messageId: String, newContent: String) { viewModelScope.launch { // Локальное редактирование repository.editLocalMessage(messageId, newContent) // UI обновляется мгновенно // WorkManager отправит изменения на сервер в фоне } } ``` ## 6. Отслеживание статуса синхронизации ```kotlin // Наблюдение за сообщениями, ожидающими синхронизации 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. Обработка ошибок синхронизации ```kotlin 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. Прочтение сообщений ```kotlin fun markAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) { viewModelScope.launch { // Отправляем статус прочтения через SignalR repository.markMessagesAsRead(chatId, lastMessageId, lastReadSequenceId) // Локальная база обновляется автоматически } } ``` ## 9. Real-time обновления SignalR события обрабатываются автоматически: - Новые сообщения появляются в UI мгновенно - Редактирования/удаления синхронизируются - Статусы прочтения обновляются - Реакции отображаются в реальном времени ```kotlin // Обработчик SignalR уже интегрирован в ChatRepositoryImpl // Дополнительные действия можно добавить в MessageSignalRHandler ``` ## 10. Кэширование в ViewModel ```kotlin @HiltViewModel class ChatViewModel @Inject constructor( private val repository: ChatRepository, savedStateHandle: SavedStateHandle ) : ViewModel() { private val chatId: String = savedStateHandle["chatId"] ?: "" // Кэшируем PagingData в scope ViewModel val messages: Flow> = repository .getMessagesPaging(chatId) .cachedIn(viewModelScope) // Важно для сохранения состояния // При повороте экрана пагинация сохраняется } ``` ## Рекомендации ### 1. Выбор между Paging 3 и Flow - **Paging 3** - для больших чатов (>100 сообщений) - **Flow** - для небольших чатов или когда нужна вся история сразу ### 2. Обработка офлайн-режима ```kotlin // 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 управлять синхронизацией