Основы Android Kotlin 08.1: Получение данных из интернета

Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .

Введение

Практически любому Android-приложению, которое вы создадите, в какой-то момент потребуется подключение к Интернету. В этой и последующих практических работах вы создадите приложение, подключающееся к веб-сервису для извлечения и отображения данных. Вы также будете опираться на знания, полученные в предыдущих практических работах о ViewModel , LiveData и RecyclerView .

В этой лабораторной работе вы будете использовать библиотеки, разработанные сообществом, для построения сетевого уровня. Это значительно упрощает получение данных и изображений, а также помогает приложению соответствовать некоторым лучшим практикам Android, таким как загрузка изображений в фоновом потоке и кэширование загруженных изображений. Для асинхронных или неблокирующих разделов кода, таких как взаимодействие со слоем веб-сервисов, вы модифицируете приложение для использования сопрограмм Kotlin. Вы также обновите пользовательский интерфейс приложения в случае низкой скорости или отсутствия интернета, чтобы пользователь был в курсе происходящего.

Что вам уже следует знать

  • Как создавать и использовать фрагменты.
  • Как перемещаться между фрагментами и использовать safeArgs для передачи данных между фрагментами.
  • Как использовать компоненты архитектуры, включая ViewModel , ViewModelProvider.Factory , LiveData и преобразования LiveData .
  • Как использовать сопрограммы для длительных задач.

Чему вы научитесь

  • Что такое REST-веб-сервис.
  • Использование библиотеки Retrofit для подключения к веб-службе REST в Интернете и получения ответа.
  • Использование библиотеки Moshi для преобразования JSON-ответа в объект данных.

Что ты будешь делать?

  • Измените стартовое приложение так, чтобы оно делало запросы к API веб-сервиса и обрабатывало ответы.
  • Реализуйте сетевой уровень для своего приложения с помощью библиотеки Retrofit.
  • Преобразуйте JSON-ответ от веб-сервиса в актуальные данные вашего приложения с помощью библиотеки Moshi.
  • Используйте поддержку сопрограмм Retrofit для упрощения кода.

В этой (и последующих) практических работах вы будете работать с базовым приложением MarsRealEstate, которое показывает недвижимость, выставленную на продажу на Марсе. Это приложение подключается к веб-сервису для извлечения и отображения данных об объектах недвижимости, включая такие сведения, как цена и доступность недвижимости для продажи или аренды. Изображения каждого объекта недвижимости — это реальные фотографии с Марса, сделанные марсоходами NASA.

Версия приложения, которую вы создадите в этой практической работе, не будет содержать много визуальных эффектов: она сосредоточена на сетевом уровне приложения для подключения к интернету и загрузки необработанных данных о недвижимости через веб-сервис. Чтобы гарантировать корректное извлечение и анализ данных, вы просто выведете количество объектов недвижимости на Марсе в текстовом виде:

.

Архитектура приложения MarsRealEstate состоит из двух основных модулей:

  • Обзорный фрагмент, содержащий сетку миниатюрных изображений объектов недвижимости, созданную с помощью RecyclerView .
  • Фрагмент подробного вида, содержащий информацию о каждом объекте недвижимости.

Приложение имеет ViewModel для каждого фрагмента. В этой лабораторной работе вы создаёте слой для сетевой службы, и ViewModel взаимодействует напрямую с этим сетевым слоем. Это похоже на то, что вы делали в предыдущих лабораторных работах, когда ViewModel взаимодействовала с базой данных Room .

Обзорная ViewModel отвечает за сетевой вызов для получения информации о недвижимости Mars. Подробная ViewModel содержит сведения об отдельном объекте недвижимости Mars, отображаемом во фрагменте сведений. Для каждой ViewModel используется LiveData с привязкой данных, учитывающей жизненный цикл, для обновления пользовательского интерфейса приложения при изменении данных.

Компонент Navigation используется как для навигации между двумя фрагментами, так и для передачи выбранного свойства в качестве аргумента.

В этом задании вы загрузите и запустите стартовое приложение для MarsRealEstate и ознакомитесь со структурой проекта.

Шаг 1: Изучение фрагментов и навигация

  1. Загрузите стартовое приложение MarsRealEstate и откройте его в Android Studio.
  2. Изучите app/java/MainActivity.kt . Приложение использует фрагменты для обоих экранов, поэтому единственная задача активности — загрузить её макет.
  3. Изучите app/res/layout/activity_main.xml . Макет активности является хостом для двух фрагментов, определённых в файле навигации. Этот макет создаёт экземпляр NavHostFragment и связанный с ним контроллер навигации с ресурсом nav_graph .
  4. Откройте app/res/navigation/nav_graph.xml . Здесь вы можете увидеть навигационную взаимосвязь между двумя фрагментами. StartDestination графа навигации указывает на фрагмент overviewFragment , поэтому фрагмент Overview создаётся при запуске приложения.

Шаг 2: изучение исходных файлов Kotlin и привязки данных

  1. На панели «Проект» разверните папку app > java . Обратите внимание, что приложение MarsRealEstate содержит три папки пакетов: detail , network и overview . Они соответствуют трём основным компонентам вашего приложения: фрагментам Overview и Detail , а также коду сетевого уровня.
  2. Откройте app/java/overview/OverviewFragment.kt . OverviewFragment лениво инициализирует OverviewViewModel , то есть OverviewViewModel создаётся при первом использовании.
  3. Изучите метод onCreateView() . Этот метод расширяет макет fragment_overview с помощью привязки данных, устанавливает владельца жизненного цикла привязки на себя ( this ) и присваивает ему переменную viewModel в объекте binding . Поскольку мы установили владельца жизненного цикла, все данные LiveData , используемые при привязке данных, будут автоматически отслеживаться на предмет любых изменений, и пользовательский интерфейс будет обновляться соответствующим образом.
  4. Откройте app/java/overview/OverviewViewModel . Поскольку ответ представляет собой LiveData , а мы задали жизненный цикл для переменной привязки, любые её изменения приведут к обновлению пользовательского интерфейса приложения.
  5. Изучите блок init . При создании ViewModel вызывается метод getMarsRealEstateProperties() .
  6. Изучите метод getMarsRealEstateProperties() . В этом стартовом приложении этот метод содержит ответ-заполнитель. Цель этой лабораторной работы — обновить ответ LiveData в ViewModel , используя реальные данные, полученные из интернета.
  7. Откройте app/res/layout/fragment_overview.xml . Это макет фрагмента обзора, с которым вы работаете в этой практической работе, и он включает привязку данных для модели представления. Он импортирует OverviewViewModel , а затем привязывает ответ ViewModel к TextView . В последующих практических работах вы замените текстовое представление сеткой изображений в RecyclerView .
  8. Скомпилируйте и запустите приложение. В текущей версии приложения вы видите только стартовый ответ: «Установите ответ API Mars здесь!»

Данные о недвижимости Mars хранятся на веб-сервере в виде REST -сервиса. Веб-сервисы используют архитектуру REST и построены с использованием стандартных веб-компонентов и протоколов.

Вы отправляете запрос к веб-сервису стандартизированным способом через URI. Привычный веб-URL на самом деле является типом URI, и оба они используются взаимозаменяемо в этом курсе. Например, в приложении для этого урока вы извлекаете все данные со следующего сервера:

https://android-kotlin-fun-mars-server.appspot.com

Если вы введете следующий URL-адрес в своем браузере, вы получите список всех доступных объектов недвижимости на Марсе!

https://android-kotlin-fun-mars-server.appspot.com/realestate

Ответ веб-сервиса обычно форматируется в формате JSON , формате обмена для представления структурированных данных. Подробнее о JSON вы узнаете в следующем задании, но краткое объяснение заключается в том, что объект JSON — это коллекция пар «ключ-значение», иногда называемая словарём , хэш-таблицей или ассоциативным массивом . Коллекция объектов JSON представляет собой массив JSON, и именно этот массив вы получаете в ответе от веб-сервиса.

Чтобы передать эти данные в приложение, вашему приложению необходимо установить сетевое соединение и связаться с этим сервером, а затем получить и преобразовать данные ответа в формат, удобный для использования приложением. В этой лабораторной работе для установления соединения используется клиентская REST-библиотека Retrofit .

Шаг 1: Добавьте зависимости Retrofit в Gradle

  1. Откройте build.gradle (Модуль: app) .
  2. В разделе dependencies добавьте следующие строки для библиотек Retrofit:
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"


Обратите внимание, что номера версий определены отдельно в файле Gradle проекта. Первая зависимость относится к самой библиотеке Retrofit 2, а вторая — к скалярному преобразователю Retrofit. Этот преобразователь позволяет Retrofit возвращать результат JSON в виде String . Эти две библиотеки работают вместе.

  1. Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новыми зависимостями.

Шаг 2: Реализация MarsApiService

Retrofit создаёт сетевой API для приложения на основе контента веб-сервиса. Он извлекает данные из веб-сервиса и направляет их через отдельную библиотеку-конвертер, которая умеет декодировать данные и возвращать их в виде полезных объектов. Retrofit включает встроенную поддержку популярных форматов веб-данных, таких как XML и JSON. В конечном итоге Retrofit создаёт большую часть сетевого уровня, включая критически важные функции, такие как выполнение запросов в фоновых потоках.

Класс MarsApiService содержит сетевой уровень приложения, то есть API, который ваша ViewModel будет использовать для взаимодействия с веб-сервисом. В этом классе вы реализуете API сервиса Retrofit.

  1. Откройте app/java/network/MarsApiService.kt . Сейчас файл содержит только одно: константу для базового URL-адреса веб-сервиса.
private const val BASE_URL = 
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. Чуть ниже этой константы создайте объект Retrofit с помощью конструктора Retrofit. Импортируйте retrofit2.Retrofit и retrofit2.converter.scalars.ScalarsConverterFactory по запросу.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()

Для создания API веб-сервисов Retrofit необходимы как минимум два элемента: базовый URI веб-сервиса и фабрика конвертеров. Конвертер сообщает Retrofit, что делать с данными, полученными от веб-сервиса. В данном случае вам нужно, чтобы Retrofit получил ответ JSON от веб-сервиса и вернул его в виде String . В Retrofit есть ScalarsConverter , который поддерживает строки и другие примитивные типы данных, поэтому вы вызываете addConverterFactory() в сборщике с экземпляром ScalarsConverterFactory . Наконец, вы вызываете build() для создания объекта Retrofit.

  1. Сразу под вызовом конструктора Retrofit определите интерфейс, определяющий, как Retrofit взаимодействует с веб-сервером с помощью HTTP-запросов. Импортируйте retrofit2.http.GET и retrofit2.Call по запросу.
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}

Сейчас наша цель — получить строку ответа JSON от веб-сервиса, и для этого вам нужен только один метод: getProperties() . Чтобы указать Retrofit, что должен делать этот метод, используйте аннотацию @GET и укажите путь или конечную точку для этого метода веб-сервиса. В данном случае конечная точка называется realestate . При вызове метода getProperties() Retrofit добавляет конечную точку realestate к базовому URL-адресу (который вы определили в конструкторе Retrofit) и создаёт объект Call . Этот объект Call используется для начала запроса.

  1. Ниже интерфейса MarsApiService определите публичный объект с именем MarsApi для инициализации службы Retrofit.
object MarsApi {
    val retrofitService : MarsApiService by lazy { 
       retrofit.create(MarsApiService::class.java) }
}

Метод Retrofit create() создаёт саму службу Retrofit с интерфейсом MarsApiService . Поскольку этот вызов требует больших затрат, а приложению нужен только один экземпляр службы Retrofit, вы предоставляете службу остальной части приложения с помощью открытого объекта MarsApi и лениво инициализируете службу Retrofit в нём. После завершения настройки каждый раз, когда ваше приложение вызывает MarsApi.retrofitService , оно будет получать синглтон-объект Retrofit, реализующий MarsApiService .

Шаг 3: вызов веб-службы в OverviewViewModel

  1. Откройте app/java/overview/OverviewViewModel.kt и прокрутите вниз до метода getMarsRealEstateProperties() .
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}

В этом методе вы вызовете службу Retrofit и обработаете возвращённую JSON-строку. Сейчас для ответа используется только строка-заполнитель.

  1. Удалите строку-заполнитель, которая задает ответ «Установите ответ API Mars здесь!»
  2. Внутри getMarsRealEstateProperties() добавьте код, показанный ниже. Импортируйте retrofit2.Callback и com.example.android.marsrealestate.network.MarsApi по запросу.

    Метод MarsApi.retrofitService.getProperties() возвращает объект Call . Затем вы можете вызвать enqueue() для этого объекта, чтобы запустить сетевой запрос в фоновом потоке.
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<String> {
})
  1. Щелкните по слову object , подчеркнутому красным. Выберите «Код» > «Реализовать методы» . Выберите из списка методы onResponse() и onFailure() .


    Android Studio добавляет код с TODO в каждый метод:
override fun onFailure(call: Call<String>, t: Throwable) {
       TODO("not implemented") 
}

override fun onResponse(call: Call<String>, 
   response: Response<String>) {
       TODO("not implemented") 
}
  1. В onFailure() удалите TODO и установите _response на сообщение об ошибке, как показано ниже. _response — это строка LiveData , которая определяет, что будет отображаться в текстовом представлении. Каждое состояние должно обновлять _response LiveData .

    Функция обратного вызова onFailure() вызывается при сбое ответа веб-сервиса. Для этого ответа установите статус _response на "Failure: " вместе с сообщением из аргумента Throwable .
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}
  1. В onResponse() удалите TODO и установите _response в тело ответа. Обратный вызов onResponse() вызывается при успешном выполнении запроса и возврате ответа веб-сервисом.
override fun onResponse(call: Call<String>, 
   response: Response<String>) {
      _response.value = response.body()
}

Шаг 4: Определите разрешение на доступ в Интернет

  1. Скомпилируйте и запустите приложение MarsRealEstate. Обратите внимание, что приложение немедленно закрывается с ошибкой.
  2. Откройте вкладку Logcat в Android Studio и обратите внимание на ошибку в журнале, которая начинается со строки вида:
Process: com.example.android.marsrealestate, PID: 10646
java.lang.SecurityException: Permission denied (missing INTERNET permission?)

Сообщение об ошибке указывает на то, что приложению может не хватать разрешения на доступ INTERNET . Подключение к Интернету создаёт проблемы безопасности, поэтому приложения по умолчанию не имеют доступа к Интернету. Вам необходимо явно указать Android, что приложению требуется доступ к Интернету.

  1. Откройте app/manifests/AndroidManifest.xml и добавьте эту строку перед тегом <application> :
<uses-permission android:name="android.permission.INTERNET" />
  1. Скомпилируйте и запустите приложение ещё раз. Если всё работает корректно с вашим интернет-соединением, вы увидите JSON-текст, содержащий данные о свойствах Mars.
  2. Нажмите кнопку «Назад» на вашем устройстве или эмуляторе, чтобы закрыть приложение.
  3. Переведите устройство или эмулятор в режим полета, а затем снова откройте приложение из меню «Недавние» или перезапустите приложение из Android Studio.


  1. Снова выключите режим полета.

Теперь вы получаете JSON-ответ от веб-сервиса Mars, что является отличным началом. Но на самом деле вам нужны объекты Kotlin, а не длинная JSON-строка. Существует библиотека Moshi — JSON-парсер для Android, преобразующий JSON-строку в объекты Kotlin. В Retrofit есть конвертер, работающий с Moshi, так что эта библиотека отлично подойдёт для ваших целей.

В этой задаче вы используете библиотеку Moshi с Retrofit для анализа JSON-ответа веб-сервиса в полезные объекты Mars Property Kotlin. Вы изменяете приложение так, чтобы вместо отображения необработанного JSON-кода оно отображало количество возвращённых Mars Properties.

Шаг 1: Добавьте зависимости библиотеки Moshi

  1. Откройте build.gradle (Модуль: app) .
  2. В разделе зависимостей добавьте приведённый ниже код, чтобы включить зависимости Moshi. Как и в случае с Retrofit, $version_moshi определяется отдельно в файле Gradle на уровне проекта. Эти зависимости добавляют поддержку базовой библиотеки Moshi JSON и языка Kotlin в Moshi.
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
  1. Найдите строку для скалярного преобразователя Retrofit в блоке dependencies :
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
  1. Измените эту строку, чтобы использовать converter-moshi :
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
  1. Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новыми зависимостями.

Шаг 2: Реализуйте класс данных MarsProperty

Пример записи JSON-ответа, который вы получаете от веб-сервиса, выглядит примерно так:

[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]

Приведенный выше ответ JSON представляет собой массив, обозначенный квадратными скобками. Массив содержит JSON-объекты, заключенные в фигурные скобки. Каждый объект содержит набор пар «имя-значение», разделенных двоеточиями. Имена заключаются в кавычки. Значения могут быть числами или строками, строки также заключаются в кавычки. Например, price этого объекта недвижимости составляет 450 000 долларов США, а img_src — это URL-адрес, указывающий на местоположение файла изображения на сервере.

Обратите внимание, что в приведенном выше примере каждая запись свойства Mars имеет следующие пары ключей и значений JSON:

  • price : цена недвижимости на Марсе, выраженная числом.
  • id : идентификатор свойства в виде строки.
  • type : либо "rent" , либо "buy" .
  • img_src : URL-адрес изображения в виде строки.

Moshi анализирует эти JSON-данные и преобразует их в объекты Kotlin. Для этого ему необходим класс данных Kotlin для хранения результатов анализа, поэтому следующим шагом будет создание этого класса.

  1. Откройте app/java/network/MarsProperty.kt .
  2. Замените существующее определение класса MarsProperty следующим кодом:
data class MarsProperty(
   val id: String, val img_src: String,
   val type: String,
   val price: Double
)

Обратите внимание, что каждая переменная в классе MarsProperty соответствует имени ключа в JSON-объекте. Для соответствия типам в JSON используются объекты String для всех значений, кроме price , которая имеет тип Double . Тип Double может использоваться для представления любого числа JSON.

Когда Moshi анализирует JSON, он сопоставляет ключи по имени и заполняет объекты данных соответствующими значениями.

  1. Замените строку ключа img_src на строку, показанную ниже. Импортируйте com.squareup.moshi.Json по запросу.
@Json(name = "img_src") val imgSrcUrl: String,

Иногда имена ключей в ответе JSON могут создавать путаницу со свойствами Kotlin или могут не соответствовать вашему стилю кодирования — например, в файле JSON ключ img_src использует подчеркивание, тогда как в свойствах Kotlin обычно используются заглавные и строчные буквы («camelcase»).

Чтобы использовать имена переменных в классе данных, отличающиеся от имён ключей в JSON-ответе, используйте аннотацию @Json . В этом примере имя переменной в классе данных — imgSrcUrl . Переменная сопоставляется с JSON-атрибутом img_src с помощью @Json(name = "img_src") .

Шаг 3: Обновите MarsApiService и OverviewViewModel

После создания класса данных MarsProperty вы теперь можете обновить сетевой API и ViewModel , включив в них данные Moshi.

  1. Откройте network/MarsApiService.kt . Вы можете увидеть ошибки, связанные с отсутствующим классом для ScalarsConverterFactory . Это связано с изменением зависимости Retrofit, которое вы внесли на шаге 1. Вы исправите эти ошибки в ближайшее время.
  2. В начале файла, непосредственно перед конструктором Retrofit, добавьте следующий код для создания экземпляра Moshi. Импортируйте com.squareup.moshi.Moshi и com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory по запросу.
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()

Подобно тому, как вы делали с Retrofit, здесь вы создаёте объект moshi с помощью конструктора Moshi. Чтобы аннотации Moshi корректно работали с Kotlin, добавьте KotlinJsonAdapterFactory , а затем вызовите метод build() .

  1. Измените сборщик Retrofit так, чтобы он использовал MoshiConverterFactory вместо ScalarConverterFactory , и передайте ему только что созданный экземпляр moshi . Импортируйте retrofit2.converter.moshi.MoshiConverterFactory по запросу.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()
  1. Удалите также импорт для ScalarConverterFactory .

Код для удаления:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. Обновите интерфейс MarsApiService , чтобы Retrofit возвращал список объектов MarsProperty вместо возврата Call<String> .
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}
  1. Откройте OverviewViewModel.kt и прокрутите вниз до вызова getProperties().enqueue() в методе getMarsRealEstateProperties() .
  2. Измените аргумент enqueue() с Callback<String> на Callback<List<MarsProperty>> . Импортируйте com.example.android.marsrealestate.network.MarsProperty по запросу.
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<List<MarsProperty>> {
  1. В onFailure() измените аргумент с Call<String> на Call<List<MarsProperty>> :
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
  1. Внесите те же изменения в оба аргумента onResponse() :
override fun onResponse(call: Call<List<MarsProperty>>, 
   response: Response<List<MarsProperty>>) {
  1. В теле метода onResponse() замените существующее назначение _response.value назначением, показанным ниже. Поскольку response.body() теперь представляет собой список объектов MarsProperty , размер этого списка равен количеству обработанных свойств. Это сообщение об ответе выводит это количество свойств:
_response.value = 
   "Success: ${response.body()?.size} Mars properties retrieved"
  1. Убедитесь, что режим «В самолёте» отключён. Скомпилируйте и запустите приложение. На этот раз сообщение должно отображать количество свойств, возвращённых веб-сервисом:

Теперь служба Retrofit API запущена, но использует обратный вызов с двумя методами обратного вызова, которые вам пришлось реализовать. Один метод обрабатывает успешный запуск, а другой — неудачный, а результат неудачи выдаёт исключения. Ваш код был бы эффективнее и проще для чтения, если бы вы могли использовать сопрограммы с обработкой исключений вместо обратных вызовов. Для удобства Retrofit предлагает библиотеку, интегрирующую сопрограммы.

В этой задаче вы преобразуете свою сетевую службу и ViewModel для использования сопрограмм.

Шаг 1: Добавьте зависимости сопрограмм

  1. Откройте build.gradle (Модуль: app) .
  2. В разделе зависимостей добавьте поддержку основных библиотек сопрограмм Kotlin и библиотеки сопрограмм Retrofit:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines"

implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
  1. Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новыми зависимостями.

Шаг 2: обновление MarsApiService и OverviewViewModel

  1. В MarsApiService.kt обновите сборщик Retrofit, чтобы использовать CoroutineCallAdapterFactory . Полный сборщик теперь выглядит так:
private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl(BASE_URL)
        .build()

Адаптеры вызовов позволяют Retrofit создавать API, возвращающие не только класс Call по умолчанию. В этом случае CoroutineCallAdapterFactory позволяет заменить объект Call , возвращаемый getProperties() на объект Deferred .

  1. В методе getProperties() измените Call<List<MarsProperty>> на Deferred<List<MarsProperty>> . Импортируйте kotlinx.coroutines.Deferred по запросу. Полный метод getProperties() выглядит так:
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>

Интерфейс Deferred определяет сопрограмму job, которая возвращает результирующее значение ( Deferred наследует от Job ). Интерфейс Deferred включает метод await() , который заставляет код ожидать без блокировки, пока значение не будет готово, а затем это значение возвращается.

  1. Откройте OverviewViewModel.kt . Перед блоком init добавьте задание сопрограммы:
private var viewModelJob = Job()
  1. Создайте область сопрограммы для этого нового задания, используя основной диспетчер:
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )

Диспетчер Dispatchers.Main использует поток пользовательского интерфейса для своей работы. Поскольку Retrofit выполняет всю работу в фоновом потоке, нет необходимости использовать какой-либо другой поток для области действия. Это позволяет легко обновлять значение MutableLiveData при получении результата.

  1. Удалите весь код внутри getMarsRealEstateProperties() . Здесь вы будете использовать сопрограммы вместо вызова enqueue() и обратных вызовов onFailure() и onResponse() .
  2. Внутри getMarsRealEstateProperties() запустите сопрограмму:
coroutineScope.launch { 

}


Чтобы использовать объект Deferred , возвращаемый Retrofit для сетевой задачи, необходимо находиться внутри сопрограммы, поэтому здесь вы запускаете только что созданную сопрограмму. Вы по-прежнему выполняете код в основном потоке, но теперь позволяете сопрограммам управлять параллельным выполнением.

  1. Внутри блока запуска вызовите getProperties() для объекта retrofitService :
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()

Вызов getProperties() из службы MarsApi создает и запускает сетевой вызов в фоновом потоке, возвращая Deferred объект для этой задачи.

  1. Также внутри блока запуска добавьте блок try / catch для обработки исключений:
try {

} catch (e: Exception) {
  
}
  1. Внутри блока try {} вызовите await() для объекта Deferred :
var listResult = getPropertiesDeferred.await()

Вызов метода await() для объекта Deferred возвращает результат сетевого вызова, когда значение готово. Метод await() неблокирующий, поэтому служба API Mars извлекает данные из сети, не блокируя текущий поток, что важно, поскольку мы находимся в области действия потока пользовательского интерфейса. После завершения задачи выполнение кода продолжается с того места, где он был остановлен. Это происходит внутри блока try {} , что позволяет перехватывать исключения.

  1. Также внутри блока try {} , после метода await() , обновите ответное сообщение для успешного ответа:
_response.value = 
   "Success: ${listResult.size} Mars properties retrieved"
  1. Внутри блока catch {} обработайте ответ об ошибке:
_response.value = "Failure: ${e.message}"


Полный метод getMarsRealEstateProperties() теперь выглядит так:

private fun getMarsRealEstateProperties() {
   coroutineScope.launch {
       var getPropertiesDeferred = 
          MarsApi.retrofitService.getProperties()
       try {          
           _response.value = 
              "Success: ${listResult.size} Mars properties retrieved"
       } catch (e: Exception) {
           _response.value = "Failure: ${e.message}"
       }
   }
}
  1. Внизу класса добавьте обратный вызов onCleared() с этим кодом:
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}

Загрузка данных должна останавливаться после уничтожения ViewModel , поскольку OverviewFragment , использующий эту ViewModel , будет удален. Чтобы остановить загрузку после уничтожения ViewModel , переопределите onCleared() , чтобы отменить задание.

  1. Скомпилируйте и запустите приложение. На этот раз вы получите тот же результат, что и в предыдущем задании (отчёт о количестве свойств), но с более простым кодом и обработкой ошибок.

Проект Android Studio: MarsRealEstateNetwork

веб-сервисы REST

  • Веб-сервис — это сервис в Интернете, который позволяет вашему приложению делать запросы и получать обратно данные.
  • Распространенные веб-сервисы используют архитектуру REST . Веб-сервисы, реализующие архитектуру REST, называются RESTful- сервисами. RESTful-сервисы построены с использованием стандартных веб-компонентов и протоколов.
  • Вы делаете запрос к веб-службе REST стандартизированным способом через URI.
  • Чтобы использовать веб-сервис, приложение должно установить сетевое соединение и связаться со сервисом. Затем приложение должно получить ответ и преобразовать данные в формат, который оно может использовать.
  • Библиотека Retrofit — это клиентская библиотека, которая позволяет вашему приложению отправлять запросы к веб-службе REST.
  • Используйте конвертеры, чтобы указать Retrofit, что делать с данными, которые он отправляет веб-сервису и получает обратно. Например, конвертер ScalarsConverter обрабатывает данные веб-сервиса как String или другой примитив.
  • Чтобы разрешить приложению подключаться к Интернету, добавьте разрешение "android.permission.INTERNET" в манифест Android.

анализ JSON

  • Ответ веб-службы часто форматируется в формате JSON — общепринятом формате обмена для представления структурированных данных.
  • JSON-объект — это коллекция пар «ключ-значение». Эту коллекцию иногда называют словарём , хэш-таблицей или ассоциативным массивом .
  • Коллекция JSON-объектов представляет собой JSON-массив. Вы получаете JSON-массив в ответе от веб-сервиса.
  • Ключи в паре «ключ-значение» заключаются в кавычки. Значения могут быть числами или строками. Строки также заключаются в кавычки.
  • Библиотека Moshi — это JSON-парсер для Android, который преобразует JSON-строки в объекты Kotlin. В Retrofit есть конвертер, работающий с Moshi.
  • Moshi сопоставляет ключи в ответе JSON со свойствами в объекте данных, имеющими то же имя.
  • Чтобы использовать другое имя свойства для ключа, аннотируйте это свойство аннотацией @Json и именем ключа JSON.

Модернизация и сопрограммы

  • Адаптеры вызовов позволяют Retrofit создавать API, возвращающие нечто, отличное от класса Call по умолчанию. Используйте класс CoroutineCallAdapterFactory , чтобы заменить Call сопрограммой Deferred .
  • Используйте метод await() на объекте Deferred , чтобы заставить код сопрограммы ждать без блокировки, пока значение не будет готово, а затем вернуть значение.

Курс Udacity:

Документация для разработчиков Android:

Документация Kotlin:

Другой:

В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:

  • При необходимости задавайте домашнее задание.
  • Объясните учащимся, как следует сдавать домашние задания.
  • Оцените домашние задания.

Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.

Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.

Ответьте на эти вопросы

Вопрос 1

Какие две ключевые вещи необходимы Retrofit для создания API веб-сервисов?

▢ Базовый URI для веб-службы и GET -запрос.

▢ Базовый URI для веб-сервиса и фабрика конвертеров.

▢ Сетевое подключение к веб-сервису и токен авторизации.

▢ Фабрика конвертеров и анализатор ответа.

Вопрос 2

Какова цель библиотеки Моши?

▢ Для получения данных из веб-сервиса.

▢ Для взаимодействия с Retrofit с целью создания запроса на веб-услугу.

▢ Для анализа JSON-ответа от веб-службы в объекты данных Kotlin.

▢ Чтобы переименовать объекты Kotlin в соответствии с ключами в ответе JSON.

Вопрос 3

Для чего используются адаптеры вызова Retrofit?

▢ Они позволяют Retrofit использовать сопрограммы.

▢ Они адаптируют ответ веб-сервиса в объекты данных Kotlin.

▢ Они преобразуют вызов Retrofit в вызов веб-сервиса.

▢ Они добавляют возможность возвращать что-то, отличное от класса Call по умолчанию в Retrofit.

Начните следующий урок: 8.2 Загрузка и отображение изображений из интернета

Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .