Сохранить данные при повороте экрана android

Fragment (Фрагменты). Часть пятая

Сохранение данных

Теперь рассмотрим важный вопрос, как сохранять данные при изменении конфигурации и других операциях.

Для примера возьмём старый урок по подсчёту ворон, но на этот раз будем считать котов. Вороны вечно порхают с ветки на ветку, их сложно считать. Коты — совсем другое дело, они лежат себе на одном месте и спят. Считать их одно удовольствие.

Кажется, я поторопился со своим утверждением.

Новый проект создавать не будем, а модифицируем старый. Заодно закрепим материал.

Создадим в проекте два новых фрагмента: WithButtonFragment и WithTextViewFragment. Из названий понятно, что в первом фрагменте будет кнопка, а во втором — TextView, в котором будет отражаться информация о количестве котов.

Повторяем прошлые шаги. Создаём новый класс, наследуемся от Fragment.

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

Подключаем разметки к соответствующим фрагментам в методе onCreateView().

Для второго фрагмента напишите код самостоятельно.

Подключаем фрагменты в разметке основной активности activity_main.xml:

Не копируйте код с сайта, проделывайте операции самостоятельно через редактор в режиме Design и Text, как это объяснялось в предыдущих примерах.

Подключим кнопку в первом фрагменте.

Во втором фрагменте подключим компонент TextView и добавим метод для вывода текста.

Если мы вызовем метод changeText() с каким-нибудь текстом, то текст отобразится в TextView. Наша задача — научить фрагменты обмениваться данными через активность.

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

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

В предыдущем примере мы создавали интерфейс внутри первого фрагмента. Для разнообразия изменим подход. Создадим новый класс через New | Java Class и в диалоговом окне для поля Kind выберем Interface. Также зададим ему имя Communicator.

Интерфейс будет состоять из одного метода.

Активность должна реализовать интерфейс.

Фрагмент с кнопкой может использовать объект интерфейса для отправки данных. Инициализируем его в новом методе onActivityCreated(), который ранее не использовали. Метод сработает, когда активность будет создана и готова к работе.

При щелчках на кнопках мы вызываем метод count(), которому передаём нужную информацию.

А сам метод в активности будет выглядеть следующим образом:

Фрагмент передаёт через метод count() данные data, а активность их принимает и передаёт их в метод второго фрагмента changeText().

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

Если вы создавали пример на основе предыдущих уроков, то не поворачивайте экран. Сначала удалите (или переименуйте) файлы для альбомной ориентации, чтобы не получить ошибку и крах приложения.

Теперь переходим непосредственно к теме нашего урока. Повернув экран, мы обнаружим, что данные из текстового блока пропали. Наши подсчёты — коту под хвост!

Мы можем в новой ориентации начать новый подсчёт, но повернув устройство обратно в портретный режим, снова потеряем данные.

Мы знаем, что при поворотах активность создаётся заново. Поэтому все данные сбрасываются. Чтобы сохранить данные, у фрагментов есть соответствующие методы, схожие с подобными методами у активностей. Задействуем их.

Метод onSaveInstanceState() поможет нам. Добавим метод в первый фрагмент с кнопкой.

Параметр метода outState является объектом класса Bundle и позволяет хранить различные типы в формате «ключ-значение».

У фрагментов также есть метод onCreate(Bundle savedInstanceState), где используется объект этого же класса Bundle только под другим именем savedInstanceState. Несмотря на разные имена, речь идёт об одном и том же объекте. И сохраняя данные в методе onSaveInstanceState(), мы можем их получить в методе onCreate():

Читайте также:  Как обновить blackview андроид

При повороте фрагмент сохранит значение счётчика, перезапустится и восстановит значение счётчика. Если запустить пример, то увидим следующее. Щёлкнем несколько раз по кнопке и повернём экран. Данные сбросятся и мы снова увидим пустой текст. Но стоит нам нажать на кнопку, то увидим, что отсчёт пошёл не сначала, а продолжил со своего последнего значения. Мы видим, что первый фрагмент запоминает свои данные. А второй фрагмент пока тупит. Поможем ему.

Добавим новую переменную mData, которая будет хранить текст сообщения. У метода onCreateView() также есть параметр savedInstanceState класса Bundle, позволяющий извлечь сохранённые данные.

А текст мы сохраним в методе onSaveInstanceState(). В методе changeText() добавим строку кода, чтобы текст брался из новой переменной. Теперь при первом запуске всё работает как прежде. При повороте текст из mData сохраняется в методе putString() и восстанавливается через getString().

После этих изменений программа больше не теряет своих данных и можно спокойно считать котов. Я стал смотреть на гифку и считать. Через 8 минут 16 секунд последний кот выпрыгнул в окно и в итоге получилось 154578 котов. Перепроверьте.

Источник

Настройки и состояние приложения

Сохранение состояния приложения

В одной из предыдущих тем был рассмотрен жизненный цикл Activity в приложении на Android, где после создания Activity вызывался метод onRestoreInstanceState , который восстанавливал ее состояние, а перед завершением работы вызывался метод onSaveInstanceState , который сохранял состояние Actiity. Оба этих метода в качестве параметра принимают объект Bundle , который как раз и хранит состояние activity:

В какой ситуации могут быть уместно использование подобных методов? Банальная ситуация — переворот экрана и переход от портретной ориентации к альбомной и наоборот. Если, к примеру, графический интерфейс содержит текстовое поле для вывода TextView, и мы программно изменяем его текст, то после изменения ориентации экрана его текст может исчезнуть. Или если у нас глобальные переменные, то при изменении ориентации экрана их значения могут быть сброшены до значений по умолчанию.

Чтобы точнее понять проблему, с которой мы можем столкнуться, рассмотрим пример. Изменим файл activity_main следующим образом:

Здесь определено поле EditText, в которое вводим имя. И также определена кнопка для его сохранения.

Далее для вывода сохраненного имени предназначено поле TextView, а для получения сохраненного имени — вторая кнопка.

Теперь изменим класс MainActivity :

Для хранения имени в программе определена переменная name. При нажатии на первую кнопку сохраняем текст из EditText в переменную name, а при нажатии на вторую кнопку — обратно получаем текст из переменной name в поле TextView.

Запустим приложение введем какое-нибудь имя, сохраним и получим его в TextView:

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

И даже если мы попробуем заново получить значение из переменной name, то мы увидим, что она обнулилась:

Чтобы избежать подобных ситуаций как раз и следует сохранять и восстанавливать состояние activity. Для этого изменим код MainActivity:

В методе onSaveInstanceState() сохраняем состояние. Для этого вызываем у параметра Bundle метод putString(key, value) , первый параметр которого — ключ, а второй — значение сохраняемых данных. В данном случае мы сохраняем строку, поэтому вызываем метод putString() . Для сохранения объектов других типов данных мы можем вызвать соответствующий метод:

put() : универсальный метод, который добавляет значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу

putString() : добавляет объект типа String

putInt() : добавляет значение типа int

putByte() : добавляет значение типа byte

putChar() : добавляет значение типа char

putShort() : добавляет значение типа short

Читайте также:  Как выбрать папку для скачивания вконтакте андроид

putLong() : добавляет значение типа long

putFloat() : добавляет значение типа float

putDouble() : добавляет значение типа double

putBoolean() : добавляет значение типа boolean

putCharArray() : добавляет массив объектов char

putIntArray() : добавляет массив объектов int

putFloatArray() : добавляет массив объектов float

putSerializable() : добавляет объект интерфейса Serializable

putParcelable() : добавляет объект Parcelable

Каждый такой метод также в качестве первого параметра принимает ключа, а в качестве второго — значение.

В методе onRestoreInstanceState происходит обратный процесс — с помощью метода getString(key) по ключу получаем из сохраненного состояния строку по ключу. Соответственно для получения данных других типов мы можем использовать аналогичные методы:

get() : универсальный метод, который возвращает значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу

getString() : возвращает объект типа String

getInt() : возвращает значение типа int

getByte() : возвращает значение типа byte

getChar() : возвращает значение типа char

getShort() : возвращает значение типа short

getLong() : возвращает значение типа long

getFloat() : возвращает значение типа float

getDouble() : возвращает значение типа double

getBoolean() : возвращает значение типа boolean

getCharArray() : возвращает массив объектов char

getIntArray() : возвращает массив объектов int

getFloatArray() : возвращает массив объектов float

getSerializable() : возвращает объект интерфейса Serializable

getParcelable() : возвращает объект Parcelable

Для примера рассмотрим сохранение-получение более сложных данных. Например, объектов определенного класса. Пусть у нас есть класс User :

Класс User реализует интерфейс Serializable , поэтому мы можем сохранить его объекты с помощью метода putSerializable() , а получить с помощью метода getSerializable() .

Пусть у нас будет следующий интерфейс в activity_main.xml :

Здесь определены два поля ввода для имени и возраста соответственно.

В классе MainActivity пропишем логику сохранения и получения данных:

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

Источник

Повороты экрана в Android без боли

Важно!
Изначально в статье была реализация с ошибкой. Ошибку исправил, статью немного поправил.

Предисловие

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

И вот я узнал про последнюю недостающую деталь — Data Binding. Как-то эта библиотека прошла мимо меня в свое время, да и все статьи, что я читал (что на русском, что на английском) рассказывали не совсем про то, что мне было необходимо. И вот сейчас я хочу рассказать про реализацию приложения, когда можно будет забыть про повороты экранов вообще, все данные будут сохраняться без нашего прямого вмешательства для каждого активити.

Когда начались проблемы?

По настоящему остро я почувствовал проблему, когда в одном проекте у меня получился экран на 1500 строк xml, по дизайну и ТЗ там было целая куча различных полей, которые становились видимыми при разных условиях. Получилось 15 различных layout’ов, каждый из которых мог быть видимым или нет. Плюс к этому была еще куча различных объектов, значения которых влияют на вьюху. Можете представить уровень проблем в момент поворота экрана.

Возможное решение

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

Я назову это реактивным MVVM. Абсолютно любой экран можно представить в виде объекта: TextView — параметр String, видимость объекта или ProgressBar’а — параметр Boolean и т.д… А так же абсолютно любое действие можно представить в виде Observable: нажатие кнопки, ввод текста в EditText и т.п…

Читайте также:  Activity have menu android

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

Да начнется магия

Перед тем как начать создавать нашу активити, создадим базовые классы для активити и ViewModel’ли, где и будет происходить вся магия.

Update!
После общения в комментариях, осознал свою ошибку. Суть в том, что в моей первой реализации ничего не сериализуется, но все работает при поворотах экрана, да даже при сворачивании, разворачивании экрана. В комментариях ниже обязательно почитайте почему так происходит. Ну а я исправлю код и поправлю комментарии к нему.

Для начала, напишем базовую ViewModel:

Я уже говорил, что все что угодно можно представить как Observable? И библиотека RxBinding отлично это делает, но вот беда, мы работает не напрямую с объектами, типа EditText, а с параметрами типа ObservableField. Что бы радоваться жизни и дальше, нам необходимо написать функцию, которая будет делать из ObservableField необходимый нам Observable RxJava2:

Тут все просто, передаем на вход ObservableField и получаем Observable RxJava2. Именно для этого мы наследуем базовый класс от BaseObservable. Добавим этот метод в наш базовый класс.

Теперь напишем базовый класс для активити:

Я постарался подробно прокомментировать код, но заострю внимание на нескольких вещах.
Активити, при повороте экрана всегда уничтожается. Тогда, при восстановлении снова вызывается метод onCreate. Вот как раз в методе onCreate нам и нужно восстанавливать данные, предварительно проверив, сохраняли ли мы какие-либо данные. Сохранение данных происходит в методе onSaveInstanceState.

При повороте экрана нас интересует порядок вызовов методов, а он такой (то, что интересует нас):

1) onDestroy
2) onSaveInstanceState

Что бы не сохранять уже не нужные данные мы добавили проверку:

Дело в том, что метод isFinishing вернет true только если мы явно вызвали метод finish() в активити, либо же ОС сама уничтожила активити из-за нехватки памяти. В этих случаях нам нет необходимости сохранять данные.

Реализация приложения

Представим условную задачу: нам необходимо сделать экран, где будет 1 EditText, 1 TextView и 1 кнопка. Кнопка не должна быть кликабельной до тех пор, пока пользователь не введет в EditText цифру 7. Сама же кнопка будет считать количество нажатий на нее, отображая их через TextView.

Update!
Пишем нашу ViewModel:

Update
Вот тут и и были самые большие проблемы. Все работало и при старой реализации, ровно до того момента, пока в настройках разработчика не включить параметр «Don’t keep activities».

Что бы все работало как надо, необходимо реализовывать интерфейс Parcelable для ViewModel. По поводу реализации ничего писать не буду, только уточню еще 1 момент:

Данные-то мы возвращаем, а вот Observable мы теряем. Поэтому пришлось выводить в отдельный метод и вызывать его во всех конструкторах. Это очень быстрое решение проблемы, не было времени подумать лучше, нужно было было указать на ошибку. Если у кого-то есть идеи как реализовать это лучше, пожалуйста, поделитесь.

Теперь напишем для этой модели view:

Ну и теперь, мы пишем нашу активити:

Запускаем приложение. Кнопка не кликабельна, счетчик показывает 0. Вводим цифру 7, вертим телефон как хотим, через 2 секунды, в любом случае кнопка становится активной, тыкаем на кнопку и счетчик растет. Стираем цифру, вертим телефоном снова — кнопка все равно через 2 секунды будет не кликабельна, а счетчик не сбросится.

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

Источник

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