263 lines
8.5 KiB
Markdown
263 lines
8.5 KiB
Markdown
# Примеры использования системы кэширования
|
||
|
||
## 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<PagingData<Message>> = repository
|
||
.getMessagesPaging(chatId)
|
||
.cachedIn(viewModelScope)
|
||
|
||
// Простой Flow для небольших чатов
|
||
val messagesList: Flow<List<Message>> = 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<PagingData<Message>> = repository
|
||
.getMessagesPaging(chatId)
|
||
.cachedIn(viewModelScope) // Важно для сохранения состояния
|
||
|
||
// При повороте экрана пагинация сохраняется
|
||
}
|
||
```
|
||
|
||
## Рекомендации
|
||
|
||
### 1. Выбор между Paging 3 и Flow
|
||
- **Paging 3** - для больших чатов (>100 сообщений)
|
||
- **Flow<List>** - для небольших чатов или когда нужна вся история сразу
|
||
|
||
### 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 управлять синхронизацией
|