Android studio task list

Полный список

— немного теории по Task
— фиксируем Activity в состоянии Paused

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

Мы уже знаем, что приложение может содержать несколько Activity. И что Activity умеет вызывать Activity из других приложений с помощью Intent и Intent Filter. Если вы хотите отправить письмо из вашего приложения, вы вызываете Activity почтовой программы и передаете ей данные. Письмо уходит и вы возвращаетесь в ваше приложение. Создается ощущение, что все это происходило в рамках одного приложения. Такая «бесшовность» достигается за счет того, что оба Activity (ваше и почтовое) были в одном Task.

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

Механизм организации Activity в Android очень схож по реализации с навигацией в браузере. Вы находитесь в одной вкладке(Task) и открываете страницы (Activity) переходя по ссылкам (Intent). В любой момент можете вернуться на предыдущую страницу, нажав кнопку Назад. Но кнопка Вперед отсутствует, т.к. страница, на которой была нажата кнопка Назад, стирается из памяти. И надо снова нажимать ссылку, если хотим попасть на нее. Если вам надо открыть что-то новое, вы создаете новую вкладку и теперь уже в ней открываете страницы, переходите по ссылкам, возвращаетесь назад. В итоге у вас есть несколько вкладок. Большинство из них на заднем фоне, а одна (активная, с которой сейчас работаете) – на переднем.

В итоге список аналогий браузера и Android таков:

Теперь вам будет более понятен текст про Task.

Task – группа из нескольких Activity, с помощью которых пользователь выполняет определенную операцию. Обычно стартовая позиция для создания Task – это экран Домой (Home).

Находясь в Home вы вызываете какое-либо приложение из списка приложений или через ярлык. Создается Task. И Activity приложения (которое отмечено как MAIN в манифест-файле) помещается в этот Task как корневое. Task выходит на передний фон. Если же при вызове приложения, система обнаружила, что в фоне уже существует Task, соответствующий этому приложению, то она выведет его на передний план и создавать ничего не будет.

Когда Activity_A вызывает Activity_B, то Activity_B помещается на верх (в топ) Task и получает фокус. Activity_A остается в Task, но находится в состоянии Stopped (его не видно и оно не в фокусе). Далее, если пользователь жмет Back находясь в Activity_B, то Activity_B удаляется из Task и уничтожается. А Activity_A оказывается теперь на верху Task и получает фокус.

В каком порядке открывались (добавлялись в Task) Activity, в таком порядке они и содержатся в Task. Они никак специально не сортируются и не упорядочиваются внутри. Набор Activity в Task еще называют back stack. Я буду называть его просто — стэк.

Схема (с офиц.сайта) демонстрирует пример:

В верхней части то, что видит пользователь. В нижней – содержимое Task. Видно, как при вызове новых Activity они добавляются в верх стэка. А если нажата кнопка Назад, то верхнее Activity из стэка удаляется и отображается предыдущее Activity.

Допустим у нас есть Task с несколькими Activity. Он на переднем фоне, мы с ним работаем сейчас.

— если мы нажмем кнопку Home, то ничего не будет удалено, все Activity сохранятся в этом Task-е, а сам Task просто уйдет на задний фон и его всегда можно будет вызвать оттуда, снова вызвав приложение, Activity которого является корневым для Task-а. Либо можно удерживать кнопку Home и мы увидим как раз список Task-ов, которые расположены на заднем фоне.

— если же в активном Task-е несколько раз нажимать кнопку Назад, то в итоге в стэке не останется Activity, пустой Task будет удален и пользователь увидит экран Home.

Там еще как всегда куча нюансов и сложностей, но мы пока остановимся на этом и в дебри не полезем. Этих знаний вполне хватит, чтобы ответить на вопросы предыдущего урока: почему на шаге 2 MainActivity исчезло с экрана, но осталось висеть в памяти и не было уничтожено? Ведь на шаге 3 было уничтожено ActivityTwo после того, как оно пропало с экрана. А на шаге 4 было в итоге уничтожено и MainActivity. Почему шаг 2 стал исключением?

Теперь вы знаете, почему. Потому, что на шаге 2 MainActivity осталось в стэке, а ActivityTwo вставилось на верх стэка и получило фокус. Ну а на шаге 3 и 4 были удалены Activity из верха стэка, в Task не осталось Activity, и мы увидели экран Home.

Читайте также:  Интерактивная панель 75w21k u 75 android ops

Если бы мы на шаге 3 нажали не Back, а Home, то Task с обоими Activity ушел бы задний фон и ничего не было бы уничтожено.

Paused

Теперь давайте откроем проект с прошлого урока P0241_TwoActivityState. Мы хотели поймать состояние Paused для Activity. Это состояние означает, что Activity не в фокусе, но оно видно, пусть и частично. Мы можем этого добиться, если присвоим диалоговый стиль для ActivityTwo. Оно отобразится как всплывающее окно и под ним будет частично видно MainActivity – оно и будет в статусе Paused. Давайте реализуем.

Для этого открываем AndroidManifest.xml, вкладка Application, находим там ActivityTwo и справа в поле Theme пишем такой текст: @android:style/Theme.Dialog

Все сохраняем и запускаем приложение.

MainActivity: onCreate()
MainActivity: onStart()
MainActivity: onResume()

MainActivity: onPause()
ActivityTwo: onCreate()
ActivityTwo: onStart()
ActivityTwo: onResume()

Видим, что не был вызван метод onStop для MainActivity, а значит приложение не было переведено в состояние Stopped и находится в режиме Paused.

ActivityTwo: onPause()
MainActivity: onResume()
ActivityTwo: onStop()
ActivityTwo: onDestroy()

MainActivity восстановилось одним лишь вызовом onResume, а onStart не понадобился, т.к. оно было в состоянии Paused, а не Stopped.

Мы четко увидели разницу между этим примером и им же на прошлом уроке. И MainActivity у нас был в состоянии Paused.

Далее можно нажать Back, а можно Home — вы уже знаете, что произойдет в обоих случаях. По логам можно убедиться в этом.

Чтобы вернуть ActivityTwo нормальный режим отображения, зайдите снова в манифест и удалите строку из поля Theme.

Кстати, у вас уже вполне достаточно знаний, чтобы создать приложение с кучей Activity, прописать вызовы и поиграться, посмотреть логи. Тем самым закрепите темы LifeCycle и Task.

На следующем уроке:

— вызываем Activity используя неявный вызов и Intent Filter

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

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

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

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

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

Источник

Урок 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, которые могли бы быть показаны в случае ошибок. Но в целом логика должна быть примерно та же, что и в случае отсутствия данных.

Обновление списка при 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.

Читайте также:  Slash and dash android

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Метод showFilteringPopUpMenu в TasksFragment.java:

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

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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

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