При создании приложений на платформе Android используются контроллеры (в контексте MVC), которые при повороте экрана удаляются и заново создаются, при этом всё их внутреннее состояние (все параметры) теряется. Такие контроллеры называются Activity и Fragment. Причём Activity может «накладывать» на себя несколько Fragment-ов, так сказать управляет ими. Поэтому все объекты уровня View контролируются Fragment-ами.
Но что делать, если состояние нужно сохранить между поворотами экрана, при этом заполняться это состояние должно только 1 раз?
Решить данную проблему поможет паттерн “Memento”!
Рассмотрим конкретное приложение, на примере которого мы будем решать проблему, путём применения паттерна “Memento”.
Вы можете установить приложение на Android устройство и ощутить всю проблему на себе. Ссылка для скачиваения apk приложения до решения проблемы
При восстановлении состояния нельзя просто загрузить данные из уровня модели, т.к. некоторые важные данные для корректного отображения представлений создаются динамически.
Приложение предназначено для записи на стирку в общежитиях ТГУ. Приложение реализует следующий сценарий:
-
Стоит пользователю перевернуть экран в момент, когда он выбирает дату для добавления новой записи, получая список доступных записей, то виджету "выбор даты" присваивается стандартное значение (текущая дата), а список доступных записей снова запрашивается у объектов уровня Model.
-
После того, как пользователь добавил новую запись, перешёл на экран «Мои записи», у него отображается добавленная запись в разделе «Текущая запись». Если перевернуть экран, то представления получают стандартное значения (у пользователя нет текущего заказа).
Согласно паттерну “Memento”, необходимо ввести в систему 3 обязанности: Хозяин (Originator), Хранитель (Memento), Опекун (CareTaker).
С распределением обязанностей Хозяин и Хранитель проблем не возникло:
Хозяин – объект типа Fragment, состояние которого надо сохранять.
Хранитель – необходимо ввести новый класс Memento, который будет иметь такую же структуру данных, что и Fragment.
Но кому назначить выполнение обязанности Опекун? Тут стоит воспользоваться паттерном распределения обязанностей, “Information Expert”, который гласит, что необходимо назначить обязанности тому классу, который имеет достаточно информации для выполнения обязанностей.
Activity «накладывает» на себя Fragment, в свою очередь Fragment «накладывает» на себя представление. Это значит, что при перевороте экрана пересоздаётся экземпляр и Activity и Fragment.
Activity и Fragment связаны между собой следующими отношениями:
Это означает, что как только началось создание объекта Activity, следом идёт создание объекта Fragment. Как только началось удаление объекта Activity, следом идёт удаление объекта Fragment. Следовательно, Activity точно знает, когда надо восстановить состояние фрагмента, а когда его сохранить. Поэтому, согласно шаблону “Information Expert“, обязанность Опекун должен выполнять класс Activity.
В итоге получаем следующую структуру общения:
-
Activity запрашивает Memento у объекта Fragment в процессе выполнения переворота экрана, непосредственно перед уничтожением Fragment-а.
-
Fragment создает и возвращает Memento, копируя в структуры данных Memento своё текущее состояние. Activity получает это состояние и сохраняет его, используя стандартный метод сохранения данных
(onSaveInstanceState(Bundle outState)).
-
Позже, когда Fragment снова создаётся после переворота экрана, ему необходимо восстановить своё состояние. Activity возвращает Memento объекту Fragment.
-
Основываясь на информации, которая хранится в объекте Memento, Fragment изменяет свои внутренние структуры и переменные в состояние до момента удаления.
Алгоритм наложения Activity на себя Fragment-а будет отличаться только типом накладываемого Fragment-а. Следовательно, чтобы вынести повторяющийся код, применим паттерн “Шаблонный метод”.
Инкапсулируем алгоритм в операцию onCreate() абстрактного класса SingleFragmentActivity, и определяем примитивную операцию createFragment().
Необходимо, чтобы Fragment-ы, состояние которых надо сохранить, подчинялись общему интерфейсу – IsaveStateFragment, который состоит из 2 операций:
public interface ISaveStateFragment {
IMemento createMemento();
void setMemento(IMemento state);
}Это позволит реализовать 2 операции для получения и возвращения Хранителя на абстрактном уровне в SingleFragmentActivity .
public abstract class SingleFragmentActivity extends AppCompatActivity {
//Вызывается в начале создания Activity
public void restoreState(){
if (this.state != null) {
((ISaveStateFragment) fragment).setMemento(this.state);
this.state = null;
}
}//Вызывается в начале удаления Activity
public void saveState() {
this.state = ((ISaveStateFragment) fragment).createMemento();
}
}Стуктурное решение по сохранению Fragment-а MakeOrderFragment:
- Поддержка сохранения между поворотами экрана проста и понятна, при этом заполняется это состояние только 1 раз.
- Экономит время работы системы, особенно в случае загрузки большого кол-ва данных.
- Сохраняется инкапсуляция состояния Fragment-ов.
- Можно с лёгкостью выбирать какие именно фрагменты сохранять, реализуя в них интерфейс IsaveStateFragment
- Простое и безболезненное расширение классов Activity
- Все классы проекта
- Классы, затронутые реализацией паттерна "Memento"
- Приложение до применения решения
- Приложение после применения решения
- Вся структура применяемого решения


