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

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

Введение

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

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

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

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

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

Что вы узнаете

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Начальный код для этой кодовой лаборатории совпадает с кодом решения для кодовой лаборатории 6.1 Create a Room database .

  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. В пакете sleeptracker откройте SleepTrackerViewModel.kt .
  2. Проверьте класс SleepTrackerViewModel , который предоставляется вам в начальном приложении и также показан ниже. Обратите внимание, что класс расширяет AndroidViewModel() . Этот класс такой же, как ViewModel , но он принимает контекст приложения в качестве параметра и делает его доступным в качестве свойства. Это понадобится вам позже.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

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

  1. В пакете sleeptracker откройте 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 не указывает поток, в котором выполняется код. Функция приостановки может выполняться в фоновом потоке или в основном потоке.

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

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

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

Диспетчер: Диспетчер отправляет сопрограммы для запуска в различных потоках. Например, 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. Внутри блока возврата пусть сопрограмма получит сегодняшнюю ночь (самую новую ночь) из базы данных. Если время начала и окончания не совпадает, что означает, что ночь уже завершена, верните 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 , которая заключается в том, что каждый раз, когда данные в базе данных изменяются, nights LiveData обновляются, чтобы отображать последние данные. Вам никогда не нужно явно устанавливать 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 каждом изменении объекта.

Удасити курс:

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

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

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

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

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

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

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

Вопрос 1

Что из следующего является преимуществом сопрограмм:

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

вопрос 2

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

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

Вопрос 3

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

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

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

Ссылки на другие лаборатории кода в этом курсе см. на целевой странице лаборатории кода Android Kotlin Fundamentals .