Основы Android Kotlin 06.2: Корутины и комната

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

Введение

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

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

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

Вам должно быть знакомо:

  • Создание базового пользовательского интерфейса (UI) с использованием действий, фрагментов, представлений и обработчиков щелчков.
  • Навигация между фрагментами и использование safeArgs для передачи простых данных между фрагментами.
  • Просмотр моделей, просмотр фабрик моделей, преобразований и LiveData .
  • Как создать базу данных Room , создать DAO и определить сущности.
  • Будет полезно, если вы знакомы с концепциями потоковой обработки и многопроцессорности.

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

  • Как работают потоки в Android.
  • Как использовать сопрограммы Kotlin для перемещения операций с базой данных из основного потока.
  • Как отобразить форматированные данные в TextView .

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

  • Расширьте возможности приложения TrackMySleepQuality для сбора, хранения и отображения данных в базе данных и из нее.
  • Используйте сопрограммы для выполнения длительных операций с базой данных в фоновом режиме.
  • Используйте LiveData для активации навигации и отображения снэк-бара.
  • Используйте LiveData для включения и отключения кнопок.

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

Приложение имеет два экрана, представленных фрагментами, как показано на рисунке ниже.

На первом экране, показанном слева, есть кнопки для запуска и остановки отслеживания. На экране отображаются все данные о сне пользователя. Кнопка «Очистить» безвозвратно удаляет все данные, собранные приложением для пользователя.

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

Поток действий пользователя выглядит следующим образом:

  • Пользователь открывает приложение и видит экран отслеживания сна.
  • Пользователь нажимает кнопку «Старт» . Время начала фиксируется и отображается на экране. Кнопка «Старт» отключается, а кнопка «Стоп» активируется.
  • Пользователь нажимает кнопку «Стоп» . Записывается время окончания и открывается экран качества сна.
  • Пользователь выбирает значок качества сна. Экран закрывается, и на экране отслеживания отображаются время окончания сна и качество сна. Кнопка «Стоп» отключается, а кнопка «Старт» активируется. Приложение готово к следующей ночи.
  • Кнопка «Очистить» активна, когда в базе данных есть данные. При нажатии кнопки «Очистить » все данные пользователя удаляются без возможности восстановления — сообщение «Вы уверены?» не появляется.

Это приложение использует упрощённую архитектуру, как показано ниже в контексте полной архитектуры. Приложение использует только следующие компоненты:

  • Контроллер пользовательского интерфейса
  • Просмотреть модель и LiveData
  • База данных A Room

В этой задаче вы используете TextView для отображения форматированных данных отслеживания сна. (Это не окончательный интерфейс. Вы узнаете о лучшем способе в другой лабораторной работе.)

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

Шаг 1: Загрузите и запустите стартовое приложение.

  1. Загрузите приложение TrackMySleepQuality-Coroutines-Starter с GitHub.
  2. Соберите и запустите приложение. Приложение отображает пользовательский интерфейс фрагмента SleepTrackerFragment , но не отображает данные. Кнопки не реагируют на нажатия.

Шаг 2: Проверьте код

Начальный код для этой лабораторной работы такой же, как код решения для лабораторной работы 6.1 «Создание базы данных комнаты» .

  1. Откройте res/layout/activity_main.xml. Этот макет содержит фрагмент nav_host_fragment . Обратите внимание на тег <merge> .

    Тег merge можно использовать для устранения избыточных макетов при добавлении макетов, и его использование — хорошая идея. Примером избыточного макета может быть ConstraintLayout > LinearLayout > TextView, где система может исключить LinearLayout. Такая оптимизация может упростить иерархию представлений и повысить производительность приложения.
  2. В папке навигации откройте файл navigation.xml . Вы увидите два фрагмента и связывающие их навигационные действия.
  3. В папке макета дважды щёлкните по фрагменту трекера сна, чтобы увидеть его XML-макет. Обратите внимание на следующее:
  • Данные макета помещены в элемент <layout> для обеспечения привязки данных.
  • ConstraintLayout и другие представления располагаются внутри элемента <layout> .
  • Файл имеет тег-заполнитель <data> .

Стартовое приложение также предоставляет размеры, цвета и стили для пользовательского интерфейса. Приложение содержит базу данных Room , DAO и сущность SleepNight . Если вы не выполнили предыдущую практическую работу, обязательно изучите эти аспекты кода самостоятельно.

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

Шаг 1. Добавьте SleepTrackerViewModel.

  1. В пакете трекера сна откройте SleepTrackerViewModel.kt .
  2. Изучите класс SleepTrackerViewModel , который предоставляется в стартовом приложении и также показан ниже. Обратите внимание, что этот класс расширяет AndroidViewModel() . Этот класс аналогичен ViewModel , но принимает контекст приложения в качестве параметра и делает его доступным как свойство. Это понадобится вам позже.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

Шаг 2: Добавьте SleepTrackerViewModelFactory

  1. В пакете трекера сна откройте SleepTrackerViewModelFactory.kt.
  2. Изучите предоставленный вам код для фабрики, показанный ниже:
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

Обратите внимание на следующее:

  • Предоставленный SleepTrackerViewModelFactory принимает тот же аргумент, что и ViewModel , и расширяет ViewModelProvider.Factory .
  • Внутри фабрики код переопределяет create() , который принимает любой тип класса в качестве аргумента и возвращает ViewModel .
  • В теле метода create() код проверяет наличие класса SleepTrackerViewModel и, если он есть, возвращает его экземпляр. В противном случае код генерирует исключение.

Шаг 3. Обновите SleepTrackerFragment.

  1. В фрагменте SleepTrackerFragment получите ссылку на контекст приложения. Поместите ссылку в onCreateView() под binding . Вам нужна ссылка на приложение, к которому прикреплён этот фрагмент, чтобы передать её поставщику фабрики моделей представлений.

    Функция requireNotNull Kotlin выдает исключение IllegalArgumentException , если значение равно null .
val application = requireNotNull(this.activity).application
  1. Вам нужна ссылка на источник данных через ссылку на DAO. В onCreateView() перед return определите dataSource . Чтобы получить ссылку на DAO базы данных, используйте SleepDatabase.getInstance(application).sleepDatabaseDao .
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. В onCreateView() перед return создайте экземпляр viewModelFactory . Передайте ему dataSource и application .
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Теперь, когда у вас есть фабрика, получите ссылку на SleepTrackerViewModel . Параметр SleepTrackerViewModel::class.java ссылается на класс Java времени выполнения этого объекта.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. Ваш готовый код должен выглядеть так:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

Вот метод onCreateView() на данный момент:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

Шаг 4: Добавьте привязку данных для модели представления

После создания базовой ViewModel вам необходимо завершить настройку привязки данных в SleepTrackerFragment , чтобы подключить ViewModel к пользовательскому интерфейсу.


В файле макета fragment_sleep_tracker.xml :

  1. Внутри блока <data> создайте <variable> , который ссылается на класс SleepTrackerViewModel .
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

В SleepTrackerFragment :

  1. Установите текущую активность как владельца жизненного цикла привязки. Добавьте этот код в метод onCreateView() перед оператором return :
binding.setLifecycleOwner(this)
  1. Назначьте переменную привязки sleepTrackerViewModel объекту sleepTrackerViewModel . Вставьте этот код в onCreateView() , под кодом, создающим SleepTrackerViewModel :
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Вероятно, вы увидите ошибку, поскольку вам придётся пересоздать объект привязки. Очистите и пересоберите проект, чтобы устранить ошибку.
  2. Наконец, как всегда, убедитесь, что ваш код собирается и работает без ошибок.

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

Корутины обладают следующими свойствами:

  • Сопрограммы являются асинхронными и неблокируемыми.
  • Сопрограммы используют функции приостановки для того, чтобы сделать асинхронный код последовательным.

Корутины асинхронны.

Сопрограмма выполняется независимо от основных этапов выполнения вашей программы. Она может выполняться параллельно или на отдельном процессоре. Также может быть так, что пока остальная часть приложения ожидает входных данных, вы незаметно добавляете часть обработки. Один из важных аспектов асинхронности заключается в том, что вы не можете ожидать результат, пока явно не дождётесь его.

Например, у вас есть вопрос, требующий исследования, и вы просите коллегу найти ответ. Он уходит и работает над ним, выполняя работу «асинхронно» и «в отдельном потоке». Вы можете продолжать заниматься другой работой, не зависящей от ответа, пока коллега не вернётся и не сообщит вам ответ.

Сопрограммы неблокируемые.

Неблокируемость означает, что сопрограмма не блокирует основной поток или поток пользовательского интерфейса. Таким образом, сопрограммы обеспечивают пользователям максимально комфортную работу, поскольку взаимодействие с пользовательским интерфейсом всегда имеет приоритет.

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

Ключевое слово suspend — это способ в Kotlin пометить функцию или тип функции как доступный для сопрограмм. Когда сопрограмма вызывает функцию, помеченную suspend , вместо того, чтобы блокироваться до завершения функции, как при обычном вызове функции, сопрограмма приостанавливает выполнение до тех пор, пока не будет готов результат. Затем сопрограмма возобновляет выполнение с того места, где остановилась, с полученным результатом.

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

Ключевое слово suspend не определяет поток, в котором выполняется код. Функция suspend может выполняться как в фоновом потоке, так и в основном потоке.

Чтобы использовать корутины в Kotlin, вам нужны три вещи:

  • Работа
  • Диспетчер
  • Область применения

Задание : По сути, задание — это всё, что можно отменить. У каждой сопрограммы есть задание, и вы можете использовать задание для его отмены. Задания можно организовать в иерархии «родители-потомки». Отмена родительского задания немедленно отменяет все его дочерние задачи, что гораздо удобнее, чем отменять каждую сопрограмму вручную.

Dispatcher: Диспетчер отправляет сопрограммы для выполнения в различных потоках. Например, Dispatcher.Main запускает задачи в основном потоке, а Dispatcher.IO переносит блокирующие задачи ввода-вывода в общий пул потоков.

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

Вы хотите, чтобы пользователь мог взаимодействовать с данными о сне следующими способами:

  • Когда пользователь нажимает кнопку «Старт» , приложение создает новую ночь сна и сохраняет ее в базе данных.
  • Когда пользователь нажимает кнопку «Стоп» , приложение обновляет ночь, указывая время ее окончания.
  • Когда пользователь нажимает кнопку «Очистить» , приложение стирает данные в базе данных.

Эти операции с базой данных могут занять много времени, поэтому их следует выполнять в отдельном потоке.

Шаг 1: Настройка сопрограмм для операций с базой данных

При нажатии кнопки «Пуск» в приложении Sleep Tracker вам необходимо вызвать функцию в SleepTrackerViewModel , чтобы создать новый экземпляр SleepNight и сохранить его в базе данных.

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

  1. Откройте файл build.gradle уровня приложения и найдите зависимости для корутин. Для использования корутин вам понадобятся эти зависимости, которые были добавлены автоматически.

    $coroutine_version определяется в файле build.gradle проекта как coroutine_version = '1.0.0' .
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Откройте файл SleepTrackerViewModel .
  2. В теле класса определите viewModelJob и назначьте ему экземпляр Job . Этот viewModelJob позволяет отменить все сопрограммы, запущенные этой моделью представления, когда она больше не используется и уничтожается. Таким образом, вы не получите сопрограммы, которым некуда возвращаться.
private var viewModelJob = Job()
  1. В конце тела класса переопределите onCleared() и отмените все сопрограммы. При уничтожении ViewModel вызывается onCleared() .
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Сразу под определением viewModelJob определите uiScope для сопрограмм. Область действия определяет поток, в котором будет выполняться сопрограмма, и ей также необходимо знать о задании. Чтобы получить область действия, запросите экземпляр CoroutineScope и передайте диспетчер и задание.

Использование Dispatchers.Main означает, что сопрограммы, запущенные в uiScope , будут выполняться в главном потоке. Это целесообразно для многих сопрограмм, запускаемых ViewModel , поскольку после выполнения ими некоторой обработки пользовательский интерфейс обновляется.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Под определением uiScope определите переменную tonight для хранения текущей ночи. Создайте переменную MutableLiveData , поскольку вам нужна возможность наблюдать за данными и изменять их.
private var tonight = MutableLiveData<SleepNight?>()
  1. Чтобы инициализировать переменную tonight как можно скорее, создайте блок init под определением tonight и вызовите initializeTonight() . initializeTonight() определяется на следующем шаге.
init {
   initializeTonight()
}
  1. Ниже блока init реализуйте initializeTonight() . В uiScope запустите сопрограмму. Внутри неё получите значение для tonight из базы данных, вызвав getTonightFromDatabase() , и присвойте это значение переменной tonight.value . getTonightFromDatabase() будет определён на следующем шаге.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Реализуйте метод getTonightFromDatabase() . Определите его как private suspend , которая возвращает SleepNight , допускающий значение NULL, если в данный момент не запущен SleepNight . Это приведёт к ошибке, поскольку функция должна что-то возвращать.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. Внутри тела функции getTonightFromDatabase() верните результат сопрограммы, работающей в контексте Dispatchers.IO . Используйте диспетчер ввода-вывода, поскольку получение данных из базы данных — это операция ввода-вывода, не имеющая ничего общего с пользовательским интерфейсом.
  return withContext(Dispatchers.IO) {}
  1. Внутри блока return позволяем сопрограмме получить из базы данных значение tonight (самую новую ночь). Если время начала и окончания не совпадают, то есть ночь уже закончилась, возвращаем null . В противном случае возвращаем ночь.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

Ваша готовая функция приостановки getTonightFromDatabase() должна выглядеть так. Больше ошибок быть не должно.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

Шаг 2: Добавьте обработчик щелчков для кнопки «Пуск»

Теперь можно реализовать onStartTracking() — обработчик щелчков кнопки «Пуск» . Необходимо создать новый SleepNight , добавить его в базу данных и назначить на tonight . Структура onStartTracking() будет очень похожа на initializeTonight() .

  1. Начните с определения функции onStartTracking() . Обработчики щелчков можно разместить над onCleared() в файле SleepTrackerViewModel .
fun onStartTracking() {}
  1. Внутри onStartTracking() запустите сопрограмму в uiScope , поскольку этот результат вам нужен для продолжения и обновления пользовательского интерфейса.
uiScope.launch {}
  1. Внутри запуска сопрограммы создайте новый SleepNight , который будет использовать текущее время в качестве времени начала.
        val newNight = SleepNight()
  1. Оставаясь внутри запуска сопрограммы, вызовите insert() для добавления newNight в базу данных. Вы увидите ошибку, поскольку функция приостановки insert() ещё не определена. (Это не одноимённая функция DAO.)
       insert(newNight)
  1. Также внутри запуска сопрограммы, обновление tonight .
       tonight.value = getTonightFromDatabase()
  1. Ниже onStartTracking() определите insert() как private suspend , которая принимает SleepNight в качестве аргумента.
private suspend fun insert(night: SleepNight) {}
  1. Для тела insert() запустите сопрограмму в контексте ввода-вывода и вставьте ночь в базу данных, вызвав insert() из DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. В файле макета fragment_sleep_tracker.xml добавьте обработчик кликов для onStartTracking() к start_button , используя магию привязки данных, которую вы настроили ранее. Обозначение @{() -> function создаёт лямбда-функцию без аргументов и вызывает обработчик кликов в sleepTrackerViewModel .
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Создайте и запустите приложение. Нажмите кнопку «Старт» . Это действие создаст данные, но вы пока ничего не видите. Это нужно исправить.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

Шаг 3: Отображение данных

В SleepTrackerViewModel переменная nights ссылается на LiveData поскольку getAllNights() в DAO возвращает LiveData .

Функция Room позволяет обновлять данные LiveData nights при каждом изменении данных в базе данных. Вам не нужно явно задавать или обновлять LiveData . Room обновляет данные в соответствии с базой данных.

Однако при отображении nights в текстовом представлении будет отображаться ссылка на объект. Чтобы просмотреть содержимое объекта, преобразуйте данные в форматированную строку. Используйте карту Transformation , которая выполняется каждый раз, когда nights получает новые данные из базы данных.

  1. Откройте файл Util.kt и раскомментируйте код определения formatNights() и связанных операторов import . Чтобы раскомментировать код в Android Studio, выделите весь код, отмеченный символом // и нажмите сочетание Cmd+/ или Control+/ .
  2. Обратите внимание, что formatNights() возвращает тип Spanned , который представляет собой строку в формате HTML.
  3. Откройте strings.xml . Обратите внимание на использование CDATA для форматирования строковых ресурсов для отображения данных о сне.
  4. Откройте SleepTrackerViewModel . В классе SleepTrackerViewModel , под определением uiScope , определите переменную nights . Получите все ночи из базы данных и присвойте их переменной nights .
private val nights = database.getAllNights()
  1. Прямо под определением nights добавьте код для преобразования nights в nightsString . Используйте функцию formatNights() из Util.kt .

    Передавайте nights в функцию map() из класса Transformations . Чтобы получить доступ к строковым ресурсам, определите функцию сопоставления как вызывающую formatNights() . Предоставьте nights и объект Resources .
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Откройте файл макета fragment_sleep_tracker.xml . В TextView , в свойстве android:text , теперь можно заменить строку ресурса ссылкой на nightsString .
"@{sleepTrackerViewModel.nightsString}"
  1. Перекомпилируйте код и запустите приложение. Теперь все данные о сне и время начала должны отображаться.
  2. Нажмите кнопку «Пуск» еще несколько раз, и вы увидите больше данных.

На следующем этапе вы включите функциональность кнопки «Стоп» .

Шаг 4: Добавьте обработчик щелчков для кнопки «Стоп»

Используя тот же шаблон, что и в предыдущем шаге, реализуйте обработчик щелчков для кнопки « Стоп» в SleepTrackerViewModel.

  1. Добавьте onStopTracking() к ViewModel . Запустите сопрограмму в uiScope . Если время окончания ещё не установлено, установите endTimeMilli на текущее системное время и вызовите update() с данными о ночном времени.

    В Kotlin синтаксис label return@ указывает функцию, из которой возвращается этот оператор, среди нескольких вложенных функций.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. Реализуйте update() используя тот же шаблон, который вы использовали для реализации insert() .
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Чтобы подключить обработчик щелчков к пользовательскому интерфейсу, откройте файл макета fragment_sleep_tracker.xml и добавьте обработчик щелчков к stop_button .
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Создайте и запустите свое приложение.
  2. Нажмите «Пуск» , затем нажмите « Стоп» . Вы увидите время начала, время окончания, качество сна без значения и продолжительность сна.

Шаг 5: Добавьте обработчик щелчков для кнопки «Очистить»

  1. Аналогичным образом реализуйте onClear() и clear() .
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Чтобы подключить обработчик щелчков к пользовательскому интерфейсу, откройте fragment_sleep_tracker.xml и добавьте обработчик щелчков к clear_button .
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Создайте и запустите свое приложение.
  2. Нажмите «Очистить» , чтобы удалить все данные. Затем нажмите «Старт» и «Стоп» , чтобы создать новые данные.

Проект Android Studio: TrackMySleepQualityCoroutines

  • Используйте ViewModel , ViewModelFactory и привязку данных для настройки архитектуры пользовательского интерфейса приложения.
  • Чтобы обеспечить бесперебойную работу пользовательского интерфейса, используйте сопрограммы для длительных задач, таких как все операции с базой данных.
  • Корутины являются асинхронными и неблокируемыми. Они используют функции suspend для преобразования асинхронного кода в последовательный.
  • Когда сопрограмма вызывает функцию, отмеченную suspend , вместо того, чтобы блокироваться до завершения этой функции, как при обычном вызове функции, она приостанавливает выполнение до тех пор, пока не будет готов результат. Затем она возобновляет выполнение с того места, где остановилась, с этим результатом.
  • Разница между блокировкой и приостановкой заключается в том, что если поток заблокирован, то никакая другая работа не выполняется. Если поток приостановлен, то другая работа выполняется до тех пор, пока не будет получен результат.

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

  • По сути, задание — это всё, что можно отменить. У каждой сопрограммы есть задание, и вы можете использовать задание для отмены сопрограммы.
  • Диспетчер отправляет сопрограммы для выполнения в различных потоках. Dispatcher.Main запускает задачи в основном потоке, а Dispartcher.IO предназначен для выгрузки блокирующих задач ввода-вывода в общий пул потоков.
  • Область действия объединяет информацию, включая задание и диспетчера, для определения контекста, в котором выполняется сопрограмма. Области действия отслеживают сопрограммы.

Чтобы реализовать обработчики щелчков, запускающие операции с базой данных, следуйте этому шаблону:

  1. Запустите сопрограмму, которая выполняется в основном или пользовательском потоке, поскольку результат влияет на пользовательский интерфейс.
  2. Вызовите функцию приостановки для выполнения длительной работы, чтобы не блокировать поток пользовательского интерфейса в ожидании результата.
  3. Длительная работа не связана с пользовательским интерфейсом, поэтому переключитесь на контекст ввода-вывода. Таким образом, работа может выполняться в пуле потоков, оптимизированном и выделенном для подобных операций.
  4. Затем вызовите функцию базы данных для выполнения работы.

Используйте карту Transformations для создания строки из объекта LiveData каждый раз при изменении объекта.

Курс Udacity:

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

Другая документация и статьи:

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

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

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

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

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

Вопрос 1

Какие из следующих преимуществ имеют сопрограммы:

  • Они не блокируются.
  • Они работают асинхронно.
  • Их можно запускать в потоке, отличном от основного потока.
  • Они всегда ускоряют работу приложений.
  • Они могут использовать исключения.
  • Их можно записать и прочитать как линейный код.

Вопрос 2

Что такое функция приостановки?

  • Обычная функция, аннотированная ключевым словом suspend .
  • Функция, которую можно вызывать внутри сопрограмм.
  • Пока выполняется функция приостановки, вызывающий поток приостанавливается.
  • Функции приостановки всегда должны работать в фоновом режиме.

Вопрос 3

В чём разница между блокировкой и приостановкой обсуждения? Отметьте всё, что верно.

  • Когда выполнение блокируется, никакая другая работа не может быть выполнена в заблокированном потоке.
  • Когда выполнение приостанавливается, поток может выполнять другую работу, ожидая завершения выгруженной работы.
  • Приостановка более эффективна, поскольку потоки не будут ожидать, ничего не делая.
  • Независимо от того, заблокировано или приостановлено выполнение, оно по-прежнему ожидает результата сопрограммы, прежде чем продолжить.

Перейти к следующему уроку: 6.3 Использование LiveData для управления состояниями кнопок

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