Эта практическая работа входит в курс «Основы 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: Изучение фрагментов и навигация
- Загрузите стартовое приложение MarsRealEstate и откройте его в Android Studio.
 -  Изучите 
app/java/MainActivity.kt. Приложение использует фрагменты для обоих экранов, поэтому единственная задача активности — загрузить её макет. -  Изучите 
app/res/layout/activity_main.xml. Макет активности является хостом для двух фрагментов, определённых в файле навигации. Этот макет создаёт экземплярNavHostFragmentи связанный с ним контроллер навигации с ресурсомnav_graph. -  Откройте 
app/res/navigation/nav_graph.xml. Здесь вы можете увидеть навигационную взаимосвязь между двумя фрагментами.StartDestinationграфа навигации указывает на фрагментoverviewFragment, поэтому фрагмент Overview создаётся при запуске приложения. 
Шаг 2: изучение исходных файлов Kotlin и привязки данных
-  На панели «Проект» разверните папку app > java . Обратите внимание, что приложение MarsRealEstate содержит три папки пакетов: 
detail,networkиoverview. Они соответствуют трём основным компонентам вашего приложения: фрагментам Overview и Detail , а также коду сетевого уровня.
 -  Откройте 
app/java/overview/OverviewFragment.kt.OverviewFragmentлениво инициализируетOverviewViewModel, то естьOverviewViewModelсоздаётся при первом использовании. -  Изучите метод 
onCreateView(). Этот метод расширяет макетfragment_overviewс помощью привязки данных, устанавливает владельца жизненного цикла привязки на себя (this) и присваивает ему переменнуюviewModelв объектеbinding. Поскольку мы установили владельца жизненного цикла, все данныеLiveData, используемые при привязке данных, будут автоматически отслеживаться на предмет любых изменений, и пользовательский интерфейс будет обновляться соответствующим образом. -  Откройте 
app/java/overview/OverviewViewModel. Поскольку ответ представляет собойLiveData, а мы задали жизненный цикл для переменной привязки, любые её изменения приведут к обновлению пользовательского интерфейса приложения. -  Изучите блок 
init. При созданииViewModelвызывается методgetMarsRealEstateProperties(). -  Изучите метод 
getMarsRealEstateProperties(). В этом стартовом приложении этот метод содержит ответ-заполнитель. Цель этой лабораторной работы — обновить ответLiveDataвViewModel, используя реальные данные, полученные из интернета. -  Откройте 
app/res/layout/fragment_overview.xml. Это макет фрагмента обзора, с которым вы работаете в этой практической работе, и он включает привязку данных для модели представления. Он импортируетOverviewViewModel, а затем привязывает ответViewModelкTextView. В последующих практических работах вы замените текстовое представление сеткой изображений вRecyclerView. - Скомпилируйте и запустите приложение. В текущей версии приложения вы видите только стартовый ответ: «Установите ответ 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
- Откройте build.gradle (Модуль: app) .
 -  В разделе 
dependenciesдобавьте следующие строки для библиотек Retrofit: 
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
 Обратите внимание, что номера версий определены отдельно в файле Gradle проекта. Первая зависимость относится к самой библиотеке Retrofit 2, а вторая — к скалярному преобразователю Retrofit. Этот преобразователь позволяет Retrofit возвращать результат JSON в виде String . Эти две библиотеки работают вместе.
- Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новыми зависимостями.
 
Шаг 2: Реализация MarsApiService
Retrofit создаёт сетевой API для приложения на основе контента веб-сервиса. Он извлекает данные из веб-сервиса и направляет их через отдельную библиотеку-конвертер, которая умеет декодировать данные и возвращать их в виде полезных объектов. Retrofit включает встроенную поддержку популярных форматов веб-данных, таких как XML и JSON. В конечном итоге Retrofit создаёт большую часть сетевого уровня, включая критически важные функции, такие как выполнение запросов в фоновых потоках.
 Класс MarsApiService содержит сетевой уровень приложения, то есть API, который ваша ViewModel будет использовать для взаимодействия с веб-сервисом. В этом классе вы реализуете API сервиса Retrofit.
-  Откройте 
app/java/network/MarsApiService.kt. Сейчас файл содержит только одно: константу для базового URL-адреса веб-сервиса. 
private const val BASE_URL = 
   "https://android-kotlin-fun-mars-server.appspot.com"-  Чуть ниже этой константы создайте объект 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.
-  Сразу под вызовом конструктора 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 используется для начала запроса.
-  Ниже интерфейса 
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
-  Откройте 
app/java/overview/OverviewViewModel.ktи прокрутите вниз до методаgetMarsRealEstateProperties(). 
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}В этом методе вы вызовете службу Retrofit и обработаете возвращённую JSON-строку. Сейчас для ответа используется только строка-заполнитель.
- Удалите строку-заполнитель, которая задает ответ «Установите ответ API Mars здесь!»
 -  Внутри 
getMarsRealEstateProperties()добавьте код, показанный ниже. Импортируйтеretrofit2.Callbackиcom.example.android.marsrealestate.network.MarsApiпо запросу.
МетодMarsApi.retrofitService.getProperties()возвращает объектCall. Затем вы можете вызватьenqueue()для этого объекта, чтобы запустить сетевой запрос в фоновом потоке. 
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<String> {
})-  Щелкните по слову 
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") 
}-  В 
onFailure()удалите TODO и установите_responseна сообщение об ошибке, как показано ниже._response— это строкаLiveData, которая определяет, что будет отображаться в текстовом представлении. Каждое состояние должно обновлять_responseLiveData.
Функция обратного вызоваonFailure()вызывается при сбое ответа веб-сервиса. Для этого ответа установите статус_responseна"Failure: "вместе с сообщением из аргументаThrowable. 
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}-  В 
onResponse()удалите TODO и установите_responseв тело ответа. Обратный вызовonResponse()вызывается при успешном выполнении запроса и возврате ответа веб-сервисом. 
override fun onResponse(call: Call<String>, 
   response: Response<String>) {
      _response.value = response.body()
}Шаг 4: Определите разрешение на доступ в Интернет
-  Скомпилируйте и запустите приложение MarsRealEstate. Обратите внимание, что приложение немедленно закрывается с ошибкой. 

 - Откройте вкладку Logcat в Android Studio и обратите внимание на ошибку в журнале, которая начинается со строки вида:
 
Process: com.example.android.marsrealestate, PID: 10646 java.lang.SecurityException: Permission denied (missing INTERNET permission?)
Сообщение об ошибке указывает на то, что приложению может не хватать разрешения на доступ INTERNET . Подключение к Интернету создаёт проблемы безопасности, поэтому приложения по умолчанию не имеют доступа к Интернету. Вам необходимо явно указать Android, что приложению требуется доступ к Интернету.
-  Откройте 
app/manifests/AndroidManifest.xmlи добавьте эту строку перед тегом<application>: 
<uses-permission android:name="android.permission.INTERNET" />-  Скомпилируйте и запустите приложение ещё раз. Если всё работает корректно с вашим интернет-соединением, вы увидите JSON-текст, содержащий данные о свойствах Mars. 

 - Нажмите кнопку «Назад» на вашем устройстве или эмуляторе, чтобы закрыть приложение.
 - Переведите устройство или эмулятор в режим полета, а затем снова откройте приложение из меню «Недавние» или перезапустите приложение из Android Studio.
 

- Снова выключите режим полета.
 
Теперь вы получаете JSON-ответ от веб-сервиса Mars, что является отличным началом. Но на самом деле вам нужны объекты Kotlin, а не длинная JSON-строка. Существует библиотека Moshi — JSON-парсер для Android, преобразующий JSON-строку в объекты Kotlin. В Retrofit есть конвертер, работающий с Moshi, так что эта библиотека отлично подойдёт для ваших целей.
В этой задаче вы используете библиотеку Moshi с Retrofit для анализа JSON-ответа веб-сервиса в полезные объекты Mars Property Kotlin. Вы изменяете приложение так, чтобы вместо отображения необработанного JSON-кода оно отображало количество возвращённых Mars Properties.
Шаг 1: Добавьте зависимости библиотеки Moshi
- Откройте build.gradle (Модуль: app) .
 -  В разделе зависимостей добавьте приведённый ниже код, чтобы включить зависимости Moshi. Как и в случае с Retrofit, 
$version_moshiопределяется отдельно в файле Gradle на уровне проекта. Эти зависимости добавляют поддержку базовой библиотеки Moshi JSON и языка Kotlin в Moshi. 
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"-  Найдите строку для скалярного преобразователя Retrofit в блоке 
dependencies: 
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"-  Измените эту строку, чтобы использовать 
converter-moshi: 
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"- Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новыми зависимостями.
 
Шаг 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 для хранения результатов анализа, поэтому следующим шагом будет создание этого класса.
-  Откройте 
app/java/network/MarsProperty.kt. -  Замените существующее определение класса 
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, он сопоставляет ключи по имени и заполняет объекты данных соответствующими значениями.
-  Замените строку ключа 
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.
-  Откройте 
network/MarsApiService.kt. Вы можете увидеть ошибки, связанные с отсутствующим классом дляScalarsConverterFactory. Это связано с изменением зависимости Retrofit, которое вы внесли на шаге 1. Вы исправите эти ошибки в ближайшее время. -  В начале файла, непосредственно перед конструктором 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() .
-  Измените сборщик Retrofit так, чтобы он использовал 
MoshiConverterFactoryвместоScalarConverterFactory, и передайте ему только что созданный экземплярmoshi. Импортируйтеretrofit2.converter.moshi.MoshiConverterFactoryпо запросу. 
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()-  Удалите также импорт для 
ScalarConverterFactory. 
Код для удаления:
import retrofit2.converter.scalars.ScalarsConverterFactory-  Обновите интерфейс 
MarsApiService, чтобы Retrofit возвращал список объектовMarsPropertyвместо возвратаCall<String>. 
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}-  Откройте 
OverviewViewModel.ktи прокрутите вниз до вызоваgetProperties().enqueue()в методеgetMarsRealEstateProperties(). -  Измените аргумент 
enqueue()сCallback<String>наCallback<List<MarsProperty>>. Импортируйтеcom.example.android.marsrealestate.network.MarsPropertyпо запросу. 
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<List<MarsProperty>> {-  В 
onFailure()измените аргумент сCall<String>наCall<List<MarsProperty>>: 
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {-  Внесите те же изменения в оба аргумента 
onResponse(): 
override fun onResponse(call: Call<List<MarsProperty>>, 
   response: Response<List<MarsProperty>>) {- В теле метода 
onResponse()замените существующее назначение_response.valueназначением, показанным ниже. Посколькуresponse.body()теперь представляет собой список объектовMarsProperty, размер этого списка равен количеству обработанных свойств. Это сообщение об ответе выводит это количество свойств: 
_response.value = 
   "Success: ${response.body()?.size} Mars properties retrieved"- Убедитесь, что режим «В самолёте» отключён. Скомпилируйте и запустите приложение. На этот раз сообщение должно отображать количество свойств, возвращённых веб-сервисом: 

 
Теперь служба Retrofit API запущена, но использует обратный вызов с двумя методами обратного вызова, которые вам пришлось реализовать. Один метод обрабатывает успешный запуск, а другой — неудачный, а результат неудачи выдаёт исключения. Ваш код был бы эффективнее и проще для чтения, если бы вы могли использовать сопрограммы с обработкой исключений вместо обратных вызовов. Для удобства Retrofit предлагает библиотеку, интегрирующую сопрограммы.
 В этой задаче вы преобразуете свою сетевую службу и ViewModel для использования сопрограмм. 
Шаг 1: Добавьте зависимости сопрограмм
- Откройте build.gradle (Модуль: app) .
 - В разделе зависимостей добавьте поддержку основных библиотек сопрограмм 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"
- Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новыми зависимостями.
 
Шаг 2: обновление MarsApiService и OverviewViewModel
-  В 
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 .
-  В методе 
getProperties()изменитеCall<List<MarsProperty>>наDeferred<List<MarsProperty>>. Импортируйтеkotlinx.coroutines.Deferredпо запросу. Полный методgetProperties()выглядит так: 
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>Интерфейс Deferred определяет сопрограмму job, которая возвращает результирующее значение ( Deferred наследует от Job ). Интерфейс Deferred включает метод await() , который заставляет код ожидать без блокировки, пока значение не будет готово, а затем это значение возвращается.
-  Откройте 
OverviewViewModel.kt. Перед блокомinitдобавьте задание сопрограммы: 
private var viewModelJob = Job()- Создайте область сопрограммы для этого нового задания, используя основной диспетчер:
 
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )Диспетчер Dispatchers.Main использует поток пользовательского интерфейса для своей работы. Поскольку Retrofit выполняет всю работу в фоновом потоке, нет необходимости использовать какой-либо другой поток для области действия. Это позволяет легко обновлять значение MutableLiveData при получении результата.
-  Удалите весь код внутри 
getMarsRealEstateProperties(). Здесь вы будете использовать сопрограммы вместо вызоваenqueue()и обратных вызововonFailure()иonResponse(). -  Внутри 
getMarsRealEstateProperties()запустите сопрограмму: 
coroutineScope.launch { 
}
 Чтобы использовать объект Deferred , возвращаемый Retrofit для сетевой задачи, необходимо находиться внутри сопрограммы, поэтому здесь вы запускаете только что созданную сопрограмму. Вы по-прежнему выполняете код в основном потоке, но теперь позволяете сопрограммам управлять параллельным выполнением.
-  Внутри блока запуска вызовите 
getProperties()для объектаretrofitService: 
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()Вызов getProperties() из службы MarsApi создает и запускает сетевой вызов в фоновом потоке, возвращая Deferred объект для этой задачи.
-  Также внутри блока запуска добавьте блок 
try/catchдля обработки исключений: 
try {
} catch (e: Exception) {
  
}-  Внутри блока 
try {}вызовитеawait()для объектаDeferred: 
var listResult = getPropertiesDeferred.await()Вызов метода await() для объекта Deferred возвращает результат сетевого вызова, когда значение готово. Метод await() неблокирующий, поэтому служба API Mars извлекает данные из сети, не блокируя текущий поток, что важно, поскольку мы находимся в области действия потока пользовательского интерфейса. После завершения задачи выполнение кода продолжается с того места, где он был остановлен. Это происходит внутри блока try {} , что позволяет перехватывать исключения.
-  Также внутри блока 
try {}, после методаawait(), обновите ответное сообщение для успешного ответа: 
_response.value = 
   "Success: ${listResult.size} Mars properties retrieved"- Внутри блока 
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}"
       }
   }
}- Внизу класса добавьте обратный вызов 
onCleared()с этим кодом: 
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}Загрузка данных должна останавливаться после уничтожения ViewModel , поскольку OverviewFragment , использующий эту ViewModel , будет удален. Чтобы остановить загрузку после уничтожения ViewModel , переопределите onCleared() , чтобы отменить задание.
- Скомпилируйте и запустите приложение. На этот раз вы получите тот же результат, что и в предыдущем задании (отчёт о количестве свойств), но с более простым кодом и обработкой ошибок.
 
Проект 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. 
 Начните следующий урок: 
Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .