Список дел android java

Список дел. Устаревший способ

Напишем приложение «Список дел», при помощи которого пользователь можете заносить в список важные дела, которые нельзя забыть — купить батон хлеба, накормить кота, поздравить жену. Данные события будем сохранять в базе данных.

Логика программы проста — на основном экране приложения выводится список дел, а на втором происходит добавление нового события.

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

ToDoDatabase.java

Займёмся основной активностью. Добавим элемент меню в res/menu/main.xml с текстом Добавить. Через этот пункт мы будем переходить на вторую активность, в которой можно будет добавить новую задачу:

Добавим несколько строковых ресурсов в res/values/strings.xml:

Разметка для основной активности (список и текстовая метка, когда список пуст):

Создадим разметку для отдельного элемента списка (list_row.xml):

Создадим вторую активность EditActivity, в которой будет происходить добавление новой задачи. Разметка для этой активности

Переходим к написанию кода. Сначала код для основной активности.

Осталось написать код для второй активности:

Запускаем проект и тестируем.

Если нужно удалить задачу из списка, то вызовите контекстное меню долгим нажатием и выберите пункт Удалить.

Вы заметили, что в проекте появились зачёркнутые строчки кода у метода startManagingCursor() и конструктора SimpleCursorAdapter, которые говорят, что эти конструкции устарели.

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

Источник

Урок 34. Практика. TodoApp. Список задач.

В этом уроке разбираем экран Tasks (список задач) приложения TodoApp

Прочитайте введение, чтобы понимать, как строятся уроки.

Приложение

Рассматриваемое приложение — todoapp. Это приложение представляет из себя менеджер задач. Пользователь может создавать, редактировать, завершать и удалять задачи.

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

Отображается список задач. При нажатии на задачу открывается экран просмотра задачи. При нажатии на FAB кнопку открывается экран создания задачи. Есть возможность использовать фильтр по статусу задачи.

Рассматриваемые функции экрана:

1) Получение данных и отображение их в списке
2) Фильтр по типу задач
3) Удаление завершенных задач
4) Переход на экран создания новой задачи
5) Переход на экран просмотра данных задачи
6) Завершение задачи

При нажатии на FAB кнопку открывается экран редактирования задачи.

Рассматриваемые функции экрана:
1) Получение данных и отображение их на экране
2) Завершение задачи
3) Удаление задачи
4) Переход на экран редактирования задачи

При нажатии на FAB кнопку происходит возврат к списку задач

Экран

Рассмотрим экран со списком задач.

Функции экрана, которые мы будем разбирать:

Из Architecture Components здесь используются: ViewModel, LiveData и Data Binding.

Также здесь активно используется SingleLiveEvent. Это LiveData, который не будет слать последнее значение новым слушателям при их подключении. В основном это полезно при поворотах экрана, чтобы не было повторных срабатываний при переподключении слушателей. Например, чтобы повторно не показывался Toast или SnackBar, когда View после пересоздания снова подключается к LiveData.

Читайте также:  Обои для андроида с мужчинами

Основные компоненты:

Схема ссылок выглядит так:

Activity и фрагмент держат ссылку на ViewModel. А ViewModel держит ссылку на репозиторий. Эта схема хороша тем, что никто не держит ссылок на Activity или фрагмент. А значит, при повороте экрана нет риска возникновения утечек.

Рассмотрим, как и где создаются основные компоненты.

TasksViewModel относится к TasksActivity. При создании TasksViewModel используется фабрика ViewModelFactory, чтобы передать TasksRepository и Application context.

Используется Application, а не Activity context, чтобы ViewModel не держала ссылку на Activity.

Фрагмент получает тот же экземпляр TasksViewModel, что и Activity. Код в TasksFragment.java:

Т.е. у Activity и фрагмента есть общий объект TasksViewModel. Это важно, потому что они будут использовать его для общения друг с другом вместо колбэков.

Взаимодействие

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

Data Binding и LiveData позволяют TasksViewModel не хранить ссылку на TasksFragment (и TasksActivity). У TasksViewModel просто нет необходимости знать что-либо о TasksFragment и просить его выполнить какое-либо действие, например, отобразить данные, прогрессбар, SnackBar и т.п. Вместо этого биндинг подписывает экранные компоненты фрагмента на Observable поля в TasksViewModel. И при изменении значений этих полей, мы автоматически видим результат на экране. Или фрагмент подписывается на LiveData, находящийся в TasksViewModel, и через него получает данные или указания.

Давайте рассмотрим это все более детально.

В layout файле фрагмента TasksFragment (tasks_frag.xml) настроен Data Binding, в котором используется TasksViewModel

Отображение списка задач

Как список с данными попадает из TasksViewModel на экран?

В TasksViewModel есть поле:

В него складываются задачи, полученные из репозитория.

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

У ListView, конечно, нет атрибута items. Он создан искусственно с помощью BindingAdapter.

Из ListView достается адаптер и ему передаются данные items.

Нет данных

Как TasksViewModel показывает другое содержимое экрана в случае, если данных нет?

В TasksViewModel есть поле:

Этот boolean флаг определяет, что будет отображаться на экране: список с данными или тексты/иконки о том, что данных нет.

Реализовано это биндингом для атрибута visibility в нескольких View

Отображение процесса загрузки данных

Как отображается прогрессбар при загрузке данных?

В TasksViewModel есть поле:

Этот boolean флаг, отвечает за отображение прогрессбара во время загрузки данных.

Реализовано это биндингом. В layout этот флаг используется в ScrollChildSwipeRefreshLayout

Атрибута refreshing у ScrollChildSwipeRefreshLayout нет. Но и BindingAdapter здесь не используется. Тут сделано хитро. Когда мы передаем значение в какой-либо атрибут View, биндинг пытается в классе View найти метод set* для этого атрибута. В данном случае, биндинг будет пытаться вызвать метод setRefreshing в классе ScrollChildSwipeRefreshLayout. Такой метод есть, он включает/выключает крутилку (прогрессбар). И мы передаем туда boolean из viewmodel.dataLoading.

Отображение ошибки при загрузке данных

Как TasksViewModel показывает другое содержимое экрана в случае, если была ошибка при загрузке данных?

В TasksViewModel есть поле:

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

Читайте также:  Android studio чем отличается drawable от drawable 24

Обновление списка при pullToRefresh

Как TasksViewModel узнает о том, что был pullToRefresh?

В layout у ScrollChildSwipeRefreshLayout в параметр onRefresh передается viewmodel:

И тут снова используется BindingAdapter.

Для ScrollChildSwipeRefreshLayout вешается обработчик OnRefreshListener, в котором просим viewModel снова загрузить данные.

Отображение SnackBar

Как TasksViewModel отображает SnackBar?

В TasksViewModel есть поле:

SnackbarMessage — это расширенный SingleLiveEvent . TasksViewModel будет передавать ему ID строки. И этот ID уйдет к подписчикам.

В нашем случае подписчиком будет фрагмент. В onActivityCreated класса TasksFragment.java выполняется подписка:

Подписываемся и при получении ID строки отображаем SnackBar.

onActivityResult

Как TasksViewModel обрабатывает результаты вызовов startActivityForResult?

Из Activity будут идти вызовы startActivityForResult. Обработка результатов этих вызовов будет делегирована TasksViewModel.

Адаптер списка

Как TasksViewModel получает нажатия на элементы списка?

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

TasksViewModel передается в конструктор адаптера TasksAdapter.java:

Далее она будет использована при обработке нажатий.

В методе getView в TasksAdapter.java настраивается биндинг:

Сначала методом DataBindingUtil.inflate создается пара view + биндинг в случае, когда view == null. Если же view пришло не null, то получаем из него биндинг.

Далее создается слушатель, методы которого будут использоваться биндингом в layout. Чуть дальше увидим, где именно.

Слушатель и задача передаются в биндинг. И процесс биндинга запускается безотлагательно.

Метод binding.getRoot() вернет корневое View биндинга.

В layout файле task_item.xml прописаны вызовы обоих методов слушателя.

На onClick корневого LinearLayout висит метод onTaskClicked

А на onClick чекбокса висит onCompleteChanged

Соответственно, при нажатии на задачу в списке и при нажатии на чекбокс будут вызваны методы слушателя, которые в свою очередь вызовут методы TasksViewModel.

При разборе функций экрана мы увидим, что именно произойдет дальше. Сейчас пока цель была — рассмотреть, как нажатие на элементы списка приводит к вызову методов TasksViewModel.

Функции

1) Получение данных и отображение их в списке

Схема выглядит так:

1. Начинается все в фрагменте, в методе onResume.

Метод start приводит к вызову метода loadTasks в TasksViewModel.java. В этом методе происходит следующее:

2. Включается отображение прогрессбара

3. Запускается процесс получения данных из репозитория

Ответ получим в колбэк.

Далее могут быть два варианта: приходят данные или ошибка. Соответственно, в моем тексте будет два продолжения цепочки шагов с пункта 4.

Сначала рассмотрим вариант с данными.

4. onTasksLoaded — из репозитория приходят данные

В зависимости от установленного на экране фильтра, из полученных из репозитория задач формируется список tasksToShow.

5. Выключается прогрессбар.

6. Выключается отображение ошибки в UI (она могла быть включена во время предыдущей попытки получения данных)

7. Задачи из отфильтрованного списка tasksToShow передаются в items

Биндинг отобразит их на экране.

8. Устанавливается флаг empty в зависимости от того, есть ли данные в items.

Биндинг в зависимости от значения empty отобразит либо список, либо тексты/картинки показывающие пользователю, что данных нет.

Теперь рассмотрим вариант, когда репозиторий вернул ошибку.

Схема почти та же:

4. onDataNotAvailable — репозиторий сообщает, что что-то пошло не так

5. Включается отображение ошибки UI.

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

2) Фильтр по типу задач

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

В фрагменте по нажатию на меню фильтра вызывается popup menu. И для него вешается обработчик.

Метод showFilteringPopUpMenu в TasksFragment.java:

При нажатии на пункт меню происходит следующее:

Читайте также:  Usb wifi адаптеры для андроид

1. В зависимости от нажатого пункта popup меню, выбранный тип фильтра передается в TasksViewModel в метод setFiltering.

Метод setFiltering в TasksViewModel.java выглядит так:

Фильтр сохраняется в поле mCurrentFiltering. А под троеточиями скрыт код настройки текстов/картинок в зависимости от фильтра.

2. Вызывается метод loadTasks, который мы рассмотрели в предыдущей функции. Он сработает по той же схеме. Просто перед запуском loadTasks будет установлен фильтр mCurrentFiltering. И когда данные придут из репозитория, TasksViewModel просеет их с учетом mCurrentFiltering, и на экран попадут только нужные задачи.

3) Удаление завершенных задач

Мы можем удалять завершенные задачи из списка.

При нажатии на этот пункт меню в TasksFragment.java вызывается метод:

Содержимое метода clearCompletedTasks в TasksViewModel.java:

1.Вызов метода репозитория для удаления выполненных задач

2. Отображение SnackBar

3. Загрузка свежих данных.

4) Переход на экран создания новой задачи

Схема вызова другого Activity и получения от него результата:

1. В фрагменте по нажатию на кнопку добавления вызывается метод:

2. Содержимое метода addNewTask в TasksViewModel.java:

mNewTaskEvent — это SingleLiveEvent

TasksActivity подписалось на этот SingleLiveEvent еще при создании, в своем onCreate методе:

В основе SingleLiveEvent лежит LiveData. Мы используем Activity (this), как Lifecycle при подписке, следовательно, подписка будет учитывать состояние Activity. И когда Activity будет уничтожено, например, при повороте экрана, Observer будет отписан автоматически и никаких утечек памяти не будет.

Идет вызов Activity для создания новой задачи и ожидание ответа (т.к. startActivityForResult, а не startActivity).

4. При сохранении, в AddEditTaskActivity будет вызван код:

5. Напомню, что в TasksActivity в методе onActivityResult идет вызов TasksViewModel.handleActivityResult.

6. Когда экран создания новой задачи вернет положительный результат, будет показан SnackBar.

А данные в списке обновятся, т.к. в onResume фрагмента идет вызов метода mTasksViewModel.start().

5) Переход на экран просмотра данных задачи

Схема очень похожа на предыдущую и шаги примерно те же.

1-2. При нажатии на задачу в списке, адаптер выполнит код:

Метод getOpenTaskEvent в TasksViewModel.java возвращает mOpenTaskEvent

TasksActivity подписалось на этот SingleLiveEvent еще при создании, в своем onCreate методе:

3. Метод openTaskDetails в TasksActivity.java:

Идет вызов TaskDetailActivity для просмотра данных задачи. И ему передается ID задачи.

4-6. Результат будет обработан в TasksViewModel. Если он положительный, то будет показан SnackBar

6) Завершение задачи

По нажатию на чекбокс, задача должна сменить статус на Завершена.

1. При нажатии на чекбокс задачи в списке, адаптер выполнит код:

Содержимое метода completeTask в TasksViewModel.java:

2. Идет вызов соответствующего метода репозитория

3. Показ SnackBar.

В реализации этой функции есть ошибка — не обновляются данные в адаптере.

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

Тут надо либо вручную менять статус задачи в адаптере и вызвать notifyItemChanged, либо запрос новых данных делать.

Возможно, это еще пофиксят.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Оцените статью