Сеть и базы данных: Retrofit и Room
Retrofit превращает REST API в Kotlin-интерфейс с suspend-функциями, а Room даёт типобезопасный доступ к локальной SQLite-базе — обе библиотеки изначально дружат с корутинами.
Суть: типичное приложение берёт данные из сети через Retrofit и кэширует их локально в Room, а корутины делают оба источника асинхронными и безопасными для интерфейса.
Почти любое полезное приложение работает с данными: загружает их с сервера и хранит локально. Для сети стандарт — Retrofit. Вы описываете API интерфейсом, помечая методы HTTP-аннотациями, а Retrofit генерирует реализацию. Методы делают suspend, чтобы вызывать их из корутин.
import retrofit2.http.GET
data class Post(val id: Int, val title: String)
interface ApiService {
@GET("posts")
suspend fun getPosts(): List<Post> // suspend: работает в корутине
}Локальное хранение в Room
Room — это надстройка над SQLite. Вы описываете таблицу через @Entity, операции — через @Dao, а саму базу — через @Database. Запросы на чтение могут возвращать Flow, который автоматически шлёт новые данные при изменении таблицы — UI обновляется сам.
import androidx.room.*
import kotlinx.coroutines.flow.Flow
@Entity
data class PostEntity(
@PrimaryKey val id: Int,
val title: String
)
@Dao
interface PostDao {
@Query("SELECT * FROM PostEntity")
fun observeAll(): Flow<List<PostEntity>> // реактивный поток
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertAll(posts: List<PostEntity>)
}Репозиторий связывает источники
Хорошая практика — спрятать оба источника за репозиторием. ViewModel обращается к репозиторию и не знает, откуда пришли данные — из сети или из кэша. Это и есть слой данных в архитектуре приложения.
class PostRepository(
private val api: ApiService,
private val dao: PostDao
) {
val posts: Flow<List<PostEntity>> = dao.observeAll()
suspend fun refresh() {
val fresh = api.getPosts() // из сети
dao.upsertAll(fresh.map { PostEntity(it.id, it.title) }) // в кэш
}
}Как работает под капотом
Retrofit использует динамический прокси: по аннотациям интерфейса он строит и выполняет HTTP-запрос, а тело ответа разбирает конвертером (обычно JSON через kotlinx.serialization или Moshi). Suspend-метод выполняется на IO-пуле и не блокирует главный поток. Room генерирует код доступа на этапе компиляции, проверяя SQL-запросы заранее, — ошибка в запросе всплывёт при сборке, а не в рантайме. Flow из Room наблюдает за таблицей: любое изменение данных порождает новую эмиссию, и подписанный UI обновляется автоматически.
Слой данных (offline-first)
Сеть (Retrofit) --refresh()--> Room (кэш)
|
| Flow (наблюдение)
v
ViewModel --> UI
UI читает из кэша; сеть лишь обновляет кэшЧастые ошибки
Вызывать сеть на главном потоке. Даже с Retrofit нельзя забывать про suspend и корутины; синхронный вызов повесит UI.
Обращаться к API напрямую из composable. Сеть и база — дело слоя данных и ViewModel, а не UI; иначе теряется управление жизненным циклом.
Игнорировать ошибки сети. Запрос может упасть; оборачивайте в обработку и показывайте состояние ошибки (вспомните sealed UiState).
Best practices
- Прячьте сеть и базу за репозиторием; ViewModel работает с репозиторием.
- Делайте сетевые и DAO-методы
suspend, а наблюдение — черезFlow. - Стройте offline-first: UI читает из кэша, сеть обновляет кэш.
- Обрабатывайте ошибки сети и отражайте их в состоянии экрана.
Логику «обновить из сети, читать из кэша» удобно смоделировать на Python. Запустите врезку.
# Аналог репозитория: сеть обновляет кэш, UI читает кэш
cache = []
def fetch_from_network():
# имитация ответа сервера
return [{'id': 1, 'title': 'Первый'}, {'id': 2, 'title': 'Второй'}]
def refresh():
global cache
cache = fetch_from_network() # как Retrofit -> Room
def observe():
return [p['title'] for p in cache] # как Flow из Room
print('кэш до:', observe())
refresh()
print('кэш после refresh:', observe())Попробуй сам ▶ — добавьте обработку «сеть недоступна» (вернуть старый кэш). Это и есть преимущество offline-first: интерфейс продолжает показывать данные из кэша даже без сети.
Итог: Retrofit загружает данные из сети, Room хранит их локально, а репозиторий связывает источники в единый слой данных. Корутины и Flow делают всё это асинхронным и реактивным. Остался последний кирпич — переходы между экранами.