Эта кодовая лаборатория является частью курса Android Kotlin Fundamentals. Вы получите максимальную отдачу от этого курса, если будете последовательно работать с лабораториями кода. Все кодовые лаборатории курса перечислены на целевой странице кодовых лабораторий Android Kotlin Fundamentals .
Введение
Одним из главных приоритетов для создания безупречного взаимодействия с пользователем для вашего приложения является обеспечение того, чтобы пользовательский интерфейс всегда реагировал и работал без сбоев. Один из способов повысить производительность пользовательского интерфейса — перевести длительные задачи, такие как операции с базой данных, в фоновый режим.
В этой кодовой лаборатории вы реализуете пользовательскую часть приложения TrackMySleepQuality, используя сопрограммы Kotlin для выполнения операций с базой данных вне основного потока.
Что вы уже должны знать
Вы должны быть знакомы с:
- Создание базового пользовательского интерфейса (UI) с использованием активности, фрагментов, представлений и обработчиков кликов.
- Навигация между фрагментами и использование
safeArgs
для передачи простых данных между фрагментами. - Просмотрите модели, просмотрите фабрики моделей, преобразования и
LiveData
. - Как создать базу данных
Room
, создать DAO и определить сущности. - Будет полезно, если вы знакомы с концепциями многопоточности и многопроцессорности.
Что вы узнаете
- Как работают потоки в Android.
- Как использовать сопрограммы Kotlin для переноса операций с базой данных из основного потока.
- Как отображать отформатированные данные в
TextView
.
Что ты будешь делать
- Расширьте приложение TrackMySleepQuality для сбора, хранения и отображения данных в базе данных и из нее.
- Используйте сопрограммы для запуска длительных операций с базой данных в фоновом режиме.
- Используйте
LiveData
для запуска навигации и отображения закусочной. - Используйте
LiveData
для включения и отключения кнопок.
В этой кодовой лаборатории вы создаете модель представления, сопрограммы и части отображения данных приложения TrackMySleepQuality.
Приложение имеет два экрана, представленных фрагментами, как показано на рисунке ниже.
На первом экране, показанном слева, есть кнопки для запуска и остановки отслеживания. На экране отображаются все данные о сне пользователя. Кнопка « Очистить » безвозвратно удаляет все данные, которые приложение собрало для пользователя.
Второй экран, показанный справа, предназначен для выбора оценки качества сна. В приложении рейтинг представлен в числовом виде. В целях разработки приложение показывает как значки лиц, так и их числовые эквиваленты.
Поток пользователя выглядит следующим образом:
- Пользователь открывает приложение и видит экран отслеживания сна.
- Пользователь нажимает кнопку « Пуск ». Это записывает время начала и отображает его. Кнопка « Пуск » отключена, а кнопка « Стоп » включена.
- Пользователь нажимает кнопку « Стоп ». Это записывает время окончания и открывает экран качества сна.
- Пользователь выбирает значок качества сна. Экран закроется, и на экране отслеживания отобразится время окончания сна и качество сна. Кнопка « Стоп » отключена, а кнопка « Пуск » включена. Приложение готово к другой ночи.
- Кнопка « Очистить » активна всякий раз, когда в базе данных есть данные. Когда пользователь нажимает кнопку « Очистить », все его данные безвозвратно стираются — нет вопроса «Вы уверены?» сообщение.
Это приложение использует упрощенную архитектуру, как показано ниже в контексте полной архитектуры. Приложение использует только следующие компоненты:
- Контроллер пользовательского интерфейса
- Посмотреть модель и
LiveData
- База данных номеров
В этой задаче вы используете TextView
для отображения отформатированных данных отслеживания сна. (Это не окончательный интерфейс. Вы узнаете лучший способ в другой лаборатории кода.)
Вы можете продолжить работу с приложением TrackMySleepQuality, созданным в предыдущей лаборатории кода, или загрузить начальное приложение для этой лаборатории кода .
Шаг 1. Загрузите и запустите стартовое приложение.
- Загрузите приложение TrackMySleepQuality-Coroutines-Starter с GitHub.
- Создайте и запустите приложение. Приложение показывает пользовательский интерфейс для фрагмента
SleepTrackerFragment
, но не содержит данных. Кнопки не реагируют на нажатия.
Шаг 2. Проверьте код
Начальный код для этой кодовой лаборатории совпадает с кодом решения для кодовой лаборатории 6.1 Create a Room database .
- Откройте файл 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
- В пакете sleeptracker откройте SleepTrackerViewModel.kt .
- Проверьте класс
SleepTrackerViewModel
, который предоставляется вам в начальном приложении и также показан ниже. Обратите внимание, что класс расширяетAndroidViewModel()
. Этот класс такой же, какViewModel
, но он принимает контекст приложения в качестве параметра и делает его доступным в качестве свойства. Это понадобится вам позже.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
Шаг 2. Добавьте SleepTrackerViewModelFactory
- В пакете sleeptracker откройте 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
не указывает поток, в котором выполняется код. Функция приостановки может выполняться в фоновом потоке или в основном потоке.
Чтобы использовать сопрограммы в Kotlin, вам нужны три вещи:
- Работа
- Диспетчер
- Область применения
Работа : По сути, работа — это все, что можно отменить. У каждой сопрограммы есть задание, и вы можете использовать задание для отмены сопрограммы. Задания могут быть организованы в иерархии родитель-потомок. Отмена родительского задания немедленно отменяет все дочерние задания, что намного удобнее, чем отмена каждой сопрограммы вручную.
Диспетчер: Диспетчер отправляет сопрограммы для запуска в различных потоках. Например, 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) {}
- Внутри блока возврата пусть сопрограмма получит сегодняшнюю ночь (самую новую ночь) из базы данных. Если время начала и окончания не совпадает, что означает, что ночь уже завершена, верните
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
, которая заключается в том, что каждый раз, когда данные в базе данных изменяются, nights
LiveData
обновляются, чтобы отображать последние данные. Вам никогда не нужно явно устанавливать 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
каждом изменении объекта.
Удасити курс:
Документация для разработчиков Android:
-
RoomDatabase
- Повторное использование макетов с <include/>
-
ViewModelProvider.Factory
-
SimpleDateFormat
-
HtmlCompat
Другая документация и статьи:
- Заводская выкройка
- Лаборатория сопрограмм
- Корутины, официальная документация
- Контекст корутины и диспетчеры
-
Dispatchers
- Превышение ограничения скорости Android
-
Job
-
launch
- Возвраты и прыжки в Котлине
- CDATA означает символьные данные . CDATA означает, что данные между этими строками включают данные, которые можно интерпретировать как XML-разметку, но не следует.
В этом разделе перечислены возможные домашние задания для студентов, которые работают с этой кодовой лабораторией в рамках курса, проводимого инструктором. Инструктор должен сделать следующее:
- При необходимости задайте домашнее задание.
- Объясните учащимся, как сдавать домашние задания.
- Оценивайте домашние задания.
Преподаватели могут использовать эти предложения так мало или так часто, как они хотят, и должны свободно давать любые другие домашние задания, которые они считают подходящими.
Если вы работаете с этой кодовой лабораторией самостоятельно, не стесняйтесь использовать эти домашние задания, чтобы проверить свои знания.
Ответьте на эти вопросы
Вопрос 1
Что из следующего является преимуществом сопрограмм:
- Они не блокируют
- Они работают асинхронно.
- Их можно запускать в потоке, отличном от основного потока.
- Они всегда ускоряют работу приложения.
- Они могут использовать исключения.
- Их можно записывать и читать как линейный код.
вопрос 2
Что такое функция приостановки?
- Обычная функция, аннотированная ключевым словом
suspend
. - Функция, которую можно вызывать внутри сопрограмм.
- Пока работает функция приостановки, вызывающий поток приостанавливается.
- Функции приостановки всегда должны выполняться в фоновом режиме.
Вопрос 3
В чем разница между блокировкой и приостановкой потока? Отметьте все, что верно.
- Когда выполнение заблокировано, никакая другая работа не может быть выполнена в заблокированном потоке.
- Когда выполнение приостановлено, поток может выполнять другую работу, ожидая завершения разгруженной работы.
- Приостановка более эффективна, потому что потоки могут не ждать и ничего не делать.
- Независимо от того, заблокировано оно или приостановлено, выполнение все еще ожидает результата сопрограммы, прежде чем продолжить.
Начать следующий урок:
Ссылки на другие лаборатории кода в этом курсе см. на целевой странице лаборатории кода Android Kotlin Fundamentals .