Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .
Введение
Один из главных приоритетов для создания безупречного пользовательского опыта вашего приложения — обеспечение постоянной отзывчивости и бесперебойной работы пользовательского интерфейса. Один из способов повысить производительность пользовательского интерфейса — перенести длительные задачи, такие как операции с базой данных, в фоновый режим.
В этой лабораторной работе вы реализуете пользовательскую часть приложения TrackMySleepQuality, используя сопрограммы Kotlin для выполнения операций с базой данных вне основного потока.
Что вам уже следует знать
Вам должно быть знакомо:
- Создание базового пользовательского интерфейса (UI) с использованием действий, фрагментов, представлений и обработчиков щелчков.
- Навигация между фрагментами и использование
safeArgs
для передачи простых данных между фрагментами. - Просмотр моделей, просмотр фабрик моделей, преобразований и
LiveData
. - Как создать базу данных
Room
, создать DAO и определить сущности. - Будет полезно, если вы знакомы с концепциями потоковой обработки и многопроцессорности.
Чему вы научитесь
- Как работают потоки в Android.
- Как использовать сопрограммы Kotlin для перемещения операций с базой данных из основного потока.
- Как отобразить форматированные данные в
TextView
.
Что ты будешь делать?
- Расширьте возможности приложения TrackMySleepQuality для сбора, хранения и отображения данных в базе данных и из нее.
- Используйте сопрограммы для выполнения длительных операций с базой данных в фоновом режиме.
- Используйте
LiveData
для активации навигации и отображения снэк-бара. - Используйте
LiveData
для включения и отключения кнопок.
В этой лабораторной работе вы создадите модель представления, сопрограммы и части отображения данных приложения TrackMySleepQuality.
Приложение имеет два экрана, представленных фрагментами, как показано на рисунке ниже.
На первом экране, показанном слева, есть кнопки для запуска и остановки отслеживания. На экране отображаются все данные о сне пользователя. Кнопка «Очистить» безвозвратно удаляет все данные, собранные приложением для пользователя.
Второй экран, показанный справа, предназначен для выбора оценки качества сна. В приложении оценка представлена в числовом виде. В целях разработки приложение отображает как значки лиц, так и их числовые эквиваленты.
Поток действий пользователя выглядит следующим образом:
- Пользователь открывает приложение и видит экран отслеживания сна.
- Пользователь нажимает кнопку «Старт» . Время начала фиксируется и отображается на экране. Кнопка «Старт» отключается, а кнопка «Стоп» активируется.
- Пользователь нажимает кнопку «Стоп» . Записывается время окончания и открывается экран качества сна.
- Пользователь выбирает значок качества сна. Экран закрывается, и на экране отслеживания отображаются время окончания сна и качество сна. Кнопка «Стоп» отключается, а кнопка «Старт» активируется. Приложение готово к следующей ночи.
- Кнопка «Очистить» активна, когда в базе данных есть данные. При нажатии кнопки «Очистить » все данные пользователя удаляются без возможности восстановления — сообщение «Вы уверены?» не появляется.
Это приложение использует упрощённую архитектуру, как показано ниже в контексте полной архитектуры. Приложение использует только следующие компоненты:
- Контроллер пользовательского интерфейса
- Просмотреть модель и
LiveData
- База данных A Room
В этой задаче вы используете TextView
для отображения форматированных данных отслеживания сна. (Это не окончательный интерфейс. Вы узнаете о лучшем способе в другой лабораторной работе.)
Вы можете продолжить работу с приложением TrackMySleepQuality, которое вы создали в предыдущей лабораторной работе, или загрузить стартовое приложение для этой лабораторной работы .
Шаг 1: Загрузите и запустите стартовое приложение.
- Загрузите приложение TrackMySleepQuality-Coroutines-Starter с GitHub.
- Соберите и запустите приложение. Приложение отображает пользовательский интерфейс фрагмента
SleepTrackerFragment
, но не отображает данные. Кнопки не реагируют на нажатия.
Шаг 2: Проверьте код
Начальный код для этой лабораторной работы такой же, как код решения для лабораторной работы 6.1 «Создание базы данных комнаты» .
- Откройте res/layout/activity_main.xml. Этот макет содержит фрагмент
nav_host_fragment
. Обратите внимание на тег<merge>
.
Тегmerge
можно использовать для устранения избыточных макетов при добавлении макетов, и его использование — хорошая идея. Примером избыточного макета может быть ConstraintLayout > LinearLayout > TextView, где система может исключить LinearLayout. Такая оптимизация может упростить иерархию представлений и повысить производительность приложения. - В папке навигации откройте файл navigation.xml . Вы увидите два фрагмента и связывающие их навигационные действия.
- В папке макета дважды щёлкните по фрагменту трекера сна, чтобы увидеть его XML-макет. Обратите внимание на следующее:
- Данные макета помещены в элемент
<layout>
для обеспечения привязки данных. -
ConstraintLayout
и другие представления располагаются внутри элемента<layout>
. - Файл имеет тег-заполнитель
<data>
.
Стартовое приложение также предоставляет размеры, цвета и стили для пользовательского интерфейса. Приложение содержит базу данных Room
, DAO и сущность SleepNight
. Если вы не выполнили предыдущую практическую работу, обязательно изучите эти аспекты кода самостоятельно.
Теперь, когда у вас есть база данных и пользовательский интерфейс, вам нужно собрать данные, добавить их в базу данных и отобразить. Вся эта работа выполняется в модели представления. Модель представления вашего трекера сна будет обрабатывать нажатия кнопок, взаимодействовать с базой данных через DAO и предоставлять данные пользовательскому интерфейсу через LiveData
. Все операции с базой данных должны выполняться вне основного потока пользовательского интерфейса, и вы будете делать это с помощью сопрограмм.
Шаг 1. Добавьте SleepTrackerViewModel.
- В пакете трекера сна откройте SleepTrackerViewModel.kt .
- Изучите класс
SleepTrackerViewModel
, который предоставляется в стартовом приложении и также показан ниже. Обратите внимание, что этот класс расширяетAndroidViewModel()
. Этот класс аналогиченViewModel
, но принимает контекст приложения в качестве параметра и делает его доступным как свойство. Это понадобится вам позже.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
Шаг 2: Добавьте SleepTrackerViewModelFactory
- В пакете трекера сна откройте SleepTrackerViewModelFactory.kt.
- Изучите предоставленный вам код для фабрики, показанный ниже:
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.
- В фрагменте
SleepTrackerFragment
получите ссылку на контекст приложения. Поместите ссылку вonCreateView()
подbinding
. Вам нужна ссылка на приложение, к которому прикреплён этот фрагмент, чтобы передать её поставщику фабрики моделей представлений.
ФункцияrequireNotNull
Kotlin выдает исключениеIllegalArgumentException
, если значение равноnull
.
val application = requireNotNull(this.activity).application
- Вам нужна ссылка на источник данных через ссылку на DAO. В
onCreateView()
передreturn
определитеdataSource
. Чтобы получить ссылку на DAO базы данных, используйтеSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- В
onCreateView()
передreturn
создайте экземплярviewModelFactory
. Передайте емуdataSource
иapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Теперь, когда у вас есть фабрика, получите ссылку на
SleepTrackerViewModel
. ПараметрSleepTrackerViewModel::class.java
ссылается на класс Java времени выполнения этого объекта.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- Ваш готовый код должен выглядеть так:
// 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
:
- Внутри блока
<data>
создайте<variable>
, который ссылается на классSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
В SleepTrackerFragment
:
- Установите текущую активность как владельца жизненного цикла привязки. Добавьте этот код в метод
onCreateView()
перед операторомreturn
:
binding.setLifecycleOwner(this)
- Назначьте переменную привязки
sleepTrackerViewModel
объектуsleepTrackerViewModel
. Вставьте этот код вonCreateView()
, под кодом, создающимSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Вероятно, вы увидите ошибку, поскольку вам придётся пересоздать объект привязки. Очистите и пересоберите проект, чтобы устранить ошибку.
- Наконец, как всегда, убедитесь, что ваш код собирается и работает без ошибок.
В Kotlin сопрограммы — это способ элегантно и эффективно обрабатывать длительные задачи. Сопрограммы Kotlin позволяют преобразовывать код, основанный на обратных вызовах, в последовательный код. Последовательный код, как правило, легче читается и даже может использовать такие языковые функции, как исключения. В конечном счёте, сопрограммы и обратные вызовы делают одно и то же: они ждут результата длительной задачи и продолжают выполнение.
Корутины обладают следующими свойствами:
- Сопрограммы являются асинхронными и неблокируемыми.
- Сопрограммы используют функции приостановки для того, чтобы сделать асинхронный код последовательным.
Корутины асинхронны.
Сопрограмма выполняется независимо от основных этапов выполнения вашей программы. Она может выполняться параллельно или на отдельном процессоре. Также может быть так, что пока остальная часть приложения ожидает входных данных, вы незаметно добавляете часть обработки. Один из важных аспектов асинхронности заключается в том, что вы не можете ожидать результат, пока явно не дождётесь его.
Например, у вас есть вопрос, требующий исследования, и вы просите коллегу найти ответ. Он уходит и работает над ним, выполняя работу «асинхронно» и «в отдельном потоке». Вы можете продолжать заниматься другой работой, не зависящей от ответа, пока коллега не вернётся и не сообщит вам ответ.
Сопрограммы неблокируемые.
Неблокируемость означает, что сопрограмма не блокирует основной поток или поток пользовательского интерфейса. Таким образом, сопрограммы обеспечивают пользователям максимально комфортную работу, поскольку взаимодействие с пользовательским интерфейсом всегда имеет приоритет.
Сопрограммы используют функции приостановки для того, чтобы сделать асинхронный код последовательным.
Ключевое слово suspend
— это способ в Kotlin пометить функцию или тип функции как доступный для сопрограмм. Когда сопрограмма вызывает функцию, помеченную suspend
, вместо того, чтобы блокироваться до завершения функции, как при обычном вызове функции, сопрограмма приостанавливает выполнение до тех пор, пока не будет готов результат. Затем сопрограмма возобновляет выполнение с того места, где остановилась, с полученным результатом.
Пока сопрограмма приостановлена и ожидает результата, она разблокирует поток, в котором она выполняется. Таким образом, могут выполняться другие функции или сопрограммы.
Ключевое слово suspend
не определяет поток, в котором выполняется код. Функция suspend может выполняться как в фоновом потоке, так и в основном потоке.
Чтобы использовать корутины в Kotlin, вам нужны три вещи:
- Работа
- Диспетчер
- Область применения
Задание : По сути, задание — это всё, что можно отменить. У каждой сопрограммы есть задание, и вы можете использовать задание для его отмены. Задания можно организовать в иерархии «родители-потомки». Отмена родительского задания немедленно отменяет все его дочерние задачи, что гораздо удобнее, чем отменять каждую сопрограмму вручную.
Dispatcher: Диспетчер отправляет сопрограммы для выполнения в различных потоках. Например, Dispatcher.Main
запускает задачи в основном потоке, а Dispatcher.IO
переносит блокирующие задачи ввода-вывода в общий пул потоков.
Область действия: область действия сопрограммы определяет контекст, в котором она выполняется. Область действия объединяет информацию о задании сопрограммы и диспетчере. Области действия отслеживают сопрограммы. При запуске сопрограммы она находится «в области действия», что означает, что вы указали, какая область действия будет отслеживать сопрограмму.
Вы хотите, чтобы пользователь мог взаимодействовать с данными о сне следующими способами:
- Когда пользователь нажимает кнопку «Старт» , приложение создает новую ночь сна и сохраняет ее в базе данных.
- Когда пользователь нажимает кнопку «Стоп» , приложение обновляет ночь, указывая время ее окончания.
- Когда пользователь нажимает кнопку «Очистить» , приложение стирает данные в базе данных.
Эти операции с базой данных могут занять много времени, поэтому их следует выполнять в отдельном потоке.
Шаг 1: Настройка сопрограмм для операций с базой данных
При нажатии кнопки «Пуск» в приложении Sleep Tracker вам необходимо вызвать функцию в SleepTrackerViewModel
, чтобы создать новый экземпляр SleepNight
и сохранить его в базе данных.
Нажатие любой из кнопок запускает операцию с базой данных, например, создание или обновление SleepNight
. По этой и другим причинам для реализации обработчиков нажатий кнопок приложения используются сопрограммы.
- Откройте файл
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"
- Откройте файл
SleepTrackerViewModel
. - В теле класса определите
viewModelJob
и назначьте ему экземплярJob
. ЭтотviewModelJob
позволяет отменить все сопрограммы, запущенные этой моделью представления, когда она больше не используется и уничтожается. Таким образом, вы не получите сопрограммы, которым некуда возвращаться.
private var viewModelJob = Job()
- В конце тела класса переопределите
onCleared()
и отмените все сопрограммы. При уничтоженииViewModel
вызываетсяonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Сразу под определением
viewModelJob
определитеuiScope
для сопрограмм. Область действия определяет поток, в котором будет выполняться сопрограмма, и ей также необходимо знать о задании. Чтобы получить область действия, запросите экземплярCoroutineScope
и передайте диспетчер и задание.
Использование Dispatchers.Main
означает, что сопрограммы, запущенные в uiScope
, будут выполняться в главном потоке. Это целесообразно для многих сопрограмм, запускаемых ViewModel
, поскольку после выполнения ими некоторой обработки пользовательский интерфейс обновляется.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- Под определением
uiScope
определите переменнуюtonight
для хранения текущей ночи. Создайте переменнуюMutableLiveData
, поскольку вам нужна возможность наблюдать за данными и изменять их.
private var tonight = MutableLiveData<SleepNight?>()
- Чтобы инициализировать переменную
tonight
как можно скорее, создайте блокinit
под определениемtonight
и вызовитеinitializeTonight()
.initializeTonight()
определяется на следующем шаге.
init {
initializeTonight()
}
- Ниже блока
init
реализуйтеinitializeTonight()
. ВuiScope
запустите сопрограмму. Внутри неё получите значение дляtonight
из базы данных, вызвавgetTonightFromDatabase()
, и присвойте это значение переменнойtonight.value
.getTonightFromDatabase()
будет определён на следующем шаге.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Реализуйте метод
getTonightFromDatabase()
. Определите его какprivate suspend
, которая возвращаетSleepNight
, допускающий значение NULL, если в данный момент не запущенSleepNight
. Это приведёт к ошибке, поскольку функция должна что-то возвращать.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- Внутри тела функции
getTonightFromDatabase()
верните результат сопрограммы, работающей в контекстеDispatchers.IO
. Используйте диспетчер ввода-вывода, поскольку получение данных из базы данных — это операция ввода-вывода, не имеющая ничего общего с пользовательским интерфейсом.
return withContext(Dispatchers.IO) {}
- Внутри блока 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()
.
- Начните с определения функции
onStartTracking()
. Обработчики щелчков можно разместить надonCleared()
в файлеSleepTrackerViewModel
.
fun onStartTracking() {}
- Внутри
onStartTracking()
запустите сопрограмму вuiScope
, поскольку этот результат вам нужен для продолжения и обновления пользовательского интерфейса.
uiScope.launch {}
- Внутри запуска сопрограммы создайте новый
SleepNight
, который будет использовать текущее время в качестве времени начала.
val newNight = SleepNight()
- Оставаясь внутри запуска сопрограммы, вызовите
insert()
для добавленияnewNight
в базу данных. Вы увидите ошибку, поскольку функция приостановкиinsert()
ещё не определена. (Это не одноимённая функция DAO.)
insert(newNight)
- Также внутри запуска сопрограммы, обновление
tonight
.
tonight.value = getTonightFromDatabase()
- Ниже
onStartTracking()
определитеinsert()
какprivate suspend
, которая принимаетSleepNight
в качестве аргумента.
private suspend fun insert(night: SleepNight) {}
- Для тела
insert()
запустите сопрограмму в контексте ввода-вывода и вставьте ночь в базу данных, вызвавinsert()
из DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- В файле макета
fragment_sleep_tracker.xml
добавьте обработчик кликов дляonStartTracking()
кstart_button
, используя магию привязки данных, которую вы настроили ранее. Обозначение@{() ->
function создаёт лямбда-функцию без аргументов и вызывает обработчик кликов вsleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- Создайте и запустите приложение. Нажмите кнопку «Старт» . Это действие создаст данные, но вы пока ничего не видите. Это нужно исправить.
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
получает новые данные из базы данных.
- Откройте файл
Util.kt
и раскомментируйте код определенияformatNights()
и связанных операторовimport
. Чтобы раскомментировать код в Android Studio, выделите весь код, отмеченный символом//
и нажмите сочетаниеCmd+/
илиControl+/
. - Обратите внимание, что
formatNights()
возвращает типSpanned
, который представляет собой строку в формате HTML. - Откройте strings.xml . Обратите внимание на использование
CDATA
для форматирования строковых ресурсов для отображения данных о сне. - Откройте SleepTrackerViewModel . В классе
SleepTrackerViewModel
, под определениемuiScope
, определите переменнуюnights
. Получите все ночи из базы данных и присвойте их переменнойnights
.
private val nights = database.getAllNights()
- Прямо под определением
nights
добавьте код для преобразованияnights
вnightsString
. Используйте функциюformatNights()
изUtil.kt
.
Передавайтеnights
в функциюmap()
из классаTransformations
. Чтобы получить доступ к строковым ресурсам, определите функцию сопоставления как вызывающуюformatNights()
. Предоставьтеnights
и объектResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Откройте файл макета
fragment_sleep_tracker.xml
. ВTextView
, в свойствеandroid:text
, теперь можно заменить строку ресурса ссылкой наnightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Перекомпилируйте код и запустите приложение. Теперь все данные о сне и время начала должны отображаться.
- Нажмите кнопку «Пуск» еще несколько раз, и вы увидите больше данных.
На следующем этапе вы включите функциональность кнопки «Стоп» .
Шаг 4: Добавьте обработчик щелчков для кнопки «Стоп»
Используя тот же шаблон, что и в предыдущем шаге, реализуйте обработчик щелчков для кнопки « Стоп» в SleepTrackerViewModel.
- Добавьте
onStopTracking()
кViewModel
. Запустите сопрограмму вuiScope
. Если время окончания ещё не установлено, установитеendTimeMilli
на текущее системное время и вызовитеupdate()
с данными о ночном времени.
В Kotlin синтаксисlabel
return@
указывает функцию, из которой возвращается этот оператор, среди нескольких вложенных функций.
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- Реализуйте
update()
используя тот же шаблон, который вы использовали для реализацииinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Чтобы подключить обработчик щелчков к пользовательскому интерфейсу, откройте файл макета
fragment_sleep_tracker.xml
и добавьте обработчик щелчков кstop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Создайте и запустите свое приложение.
- Нажмите «Пуск» , затем нажмите « Стоп» . Вы увидите время начала, время окончания, качество сна без значения и продолжительность сна.
Шаг 5: Добавьте обработчик щелчков для кнопки «Очистить»
- Аналогичным образом реализуйте
onClear()
иclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Чтобы подключить обработчик щелчков к пользовательскому интерфейсу, откройте
fragment_sleep_tracker.xml
и добавьте обработчик щелчков кclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Создайте и запустите свое приложение.
- Нажмите «Очистить» , чтобы удалить все данные. Затем нажмите «Старт» и «Стоп» , чтобы создать новые данные.
Проект Android Studio: TrackMySleepQualityCoroutines
- Используйте
ViewModel
,ViewModelFactory
и привязку данных для настройки архитектуры пользовательского интерфейса приложения. - Чтобы обеспечить бесперебойную работу пользовательского интерфейса, используйте сопрограммы для длительных задач, таких как все операции с базой данных.
- Корутины являются асинхронными и неблокируемыми. Они используют функции
suspend
для преобразования асинхронного кода в последовательный. - Когда сопрограмма вызывает функцию, отмеченную
suspend
, вместо того, чтобы блокироваться до завершения этой функции, как при обычном вызове функции, она приостанавливает выполнение до тех пор, пока не будет готов результат. Затем она возобновляет выполнение с того места, где остановилась, с этим результатом. - Разница между блокировкой и приостановкой заключается в том, что если поток заблокирован, то никакая другая работа не выполняется. Если поток приостановлен, то другая работа выполняется до тех пор, пока не будет получен результат.
Для запуска сопрограммы вам понадобятся задание, диспетчер и область действия:
- По сути, задание — это всё, что можно отменить. У каждой сопрограммы есть задание, и вы можете использовать задание для отмены сопрограммы.
- Диспетчер отправляет сопрограммы для выполнения в различных потоках.
Dispatcher.Main
запускает задачи в основном потоке, аDispartcher.IO
предназначен для выгрузки блокирующих задач ввода-вывода в общий пул потоков. - Область действия объединяет информацию, включая задание и диспетчера, для определения контекста, в котором выполняется сопрограмма. Области действия отслеживают сопрограммы.
Чтобы реализовать обработчики щелчков, запускающие операции с базой данных, следуйте этому шаблону:
- Запустите сопрограмму, которая выполняется в основном или пользовательском потоке, поскольку результат влияет на пользовательский интерфейс.
- Вызовите функцию приостановки для выполнения длительной работы, чтобы не блокировать поток пользовательского интерфейса в ожидании результата.
- Длительная работа не связана с пользовательским интерфейсом, поэтому переключитесь на контекст ввода-вывода. Таким образом, работа может выполняться в пуле потоков, оптимизированном и выделенном для подобных операций.
- Затем вызовите функцию базы данных для выполнения работы.
Используйте карту Transformations
для создания строки из объекта LiveData
каждый раз при изменении объекта.
Курс Udacity:
Документация для разработчиков Android:
-
RoomDatabase
- Повторное использование макетов с помощью <include/>
-
ViewModelProvider.Factory
-
SimpleDateFormat
-
HtmlCompat
Другая документация и статьи:
- Фабричный образец
- Корутины codelab
- Корутины, официальная документация
- Контекст корутины и диспетчеры
-
Dispatchers
- Превысьте ограничение скорости Android
-
Job
-
launch
- Возвраты и прыжки в Kotlin
- CDATA означает символьные данные . CDATA означает, что данные между этими строками включают данные, которые могут быть интерпретированы как XML-разметка, но не должны таковыми.
В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:
- При необходимости задавайте домашнее задание.
- Объясните учащимся, как следует сдавать домашние задания.
- Оцените домашние задания.
Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.
Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.
Ответьте на эти вопросы
Вопрос 1
Какие из следующих преимуществ имеют сопрограммы:
- Они не блокируются.
- Они работают асинхронно.
- Их можно запускать в потоке, отличном от основного потока.
- Они всегда ускоряют работу приложений.
- Они могут использовать исключения.
- Их можно записать и прочитать как линейный код.
Вопрос 2
Что такое функция приостановки?
- Обычная функция, аннотированная ключевым словом
suspend
. - Функция, которую можно вызывать внутри сопрограмм.
- Пока выполняется функция приостановки, вызывающий поток приостанавливается.
- Функции приостановки всегда должны работать в фоновом режиме.
Вопрос 3
В чём разница между блокировкой и приостановкой обсуждения? Отметьте всё, что верно.
- Когда выполнение блокируется, никакая другая работа не может быть выполнена в заблокированном потоке.
- Когда выполнение приостанавливается, поток может выполнять другую работу, ожидая завершения выгруженной работы.
- Приостановка более эффективна, поскольку потоки не будут ожидать, ничего не делая.
- Независимо от того, заблокировано или приостановлено выполнение, оно по-прежнему ожидает результата сопрограммы, прежде чем продолжить.
Перейти к следующему уроку:
Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .