Прошло много времени, я пересмотрел свой подход к тому, как можно удобно сделать походы в интернет в Android-приложении. Мой проект системы дистанционного образования претерпел очередное изменение концепции — теперь это платформа для проведения стажировок в компаниях.
На приложении это сказалось грандиозно — сейчас я переписываю его с нуля, учитывая проблемы, которые были в старой версии. Например, здесь будет нормальная обработка ошибок (и приложение не будет вылетать на каждый неправильный чих)
Установка зависимостей
В build.gradle.kts уровня проекта добавим плагин сериализатора:
В build.gradle.kts уровня модуля добавим следующие зависимости:
Так же понадобится активировать плагин в файле уровня модуля:
Настройка клиента
В этот раз я решил избавиться от механизма, при котором пользователь выбирает базовый URL сервера, на который делаются все запросы, поэтому зададим этот адрес прямо здесь константой и настроим клиент так, чтобы он всегда использовал этот адрес для всех запросов:
Lazy позволяет не создавать клиент на старте приложения. Он будет создан при первом обращении к полю. Так, мы сможем в любом месте приложения обращаться к одному и тому же клиенту.
Типизированные ответы
В новой версии моего API сервер всегда возвращает одну структуру, которую примерно можно описать так:
При этом $.value может быть как объектом, так и списком, в зависимости от конкретного метода API. Поэтому напишем обёртку с обобщённым параметром, объект которой будем создавать при парсинге ответа:
Кроме того, понадобится упрощённая версия этого объекта, с которой уже и будет работать код, вызывающий сетевые запросы.
Отправка запросов
Теперь добавим метод расширения safeRequest к HttpClient:
Он позволяет безопасно делать запросы в сеть, обрабатывая исключения и возвращая разные объекты в зависимости от кода ответа
Кроме того, нужен ещё один метод, расширяющий HttpResponse, который парсит ответ из JSON-строки в объект ResponseWrapper<T>:
Если ваш сервер не возвращает единую структуру для всех ответов, метод можно переписать так:
Так же, нужно будет изменить код, который обращается к телу запроса через parseBody<T>. Например так:
Выполнение запросов
Запросы будут выполняться в отдельных классах сервисов. Напишем один такой:
Каждый метод в сервисе — это неблокирующая функция, которая берёт общий объект HTTP клиента и использует его, чтобы вызвать safeRequest. Она возвращает упрощённый Response<T>, где T — это объект ответа, находящийся в $.value исходного ответа.
Ответ всегда выглядит как data class с аннотацией @Serializable:
Если в модели используются типы из Java, для которых в ktor нет своего сериализатора (например UUID, который я использую для идентификаторов), потребуется написать свой сериализатор:
На поле в модели сериализатор добавляется так:
Работа с ответами
Запросы выполняются во viewModel, внедряемой в экраны, которым это нужно
Интерфейс пользователя
Ну и последний этап — добавим отображение ошибок на экране. Для этого Composable функция должна подписаться на изменения snackbarMessage из viewModel:
Здесь snackbarHostState это аргумент, который приходит из главного компонента. Создаётся он так:
Заключение
Вот так можно реализовать удобные походы в сеть с помощью ktor в вашем Android-приложении на Jetpack Compose. Полный исходный код приложения можно посмотреть в репозитории https://github.com/uni-lms/applications/tree/v2