Правильный способ сохранения и восстановления строки в различных точках жизненного цикла в EditText с использованием SharedPreferences?

Я нигде не нашел хорошего окончательного ответа на этот вопрос (может быть, я плохо ищу?)

Жизненный цикл активности Android имеет различные события, которые заставят пользователя потерять содержимое EditText, если вы не сохраните и не восстановите его во время событий жизненного цикла — как правило, с использованием общих настроек. Звучит просто, но я не могу заставить это работать на практике; несмотря на то, что содержимое EditText регистрируется при каждом событии, оно не обновляется на моем устройстве, когда я перехожу к другому приложению, а затем обратно.

У меня есть пользователь, вводящий серийный номер в поле EditText. Вот что я делаю в своей деятельности:

private lateinit var sharedPref: SharedPreferences

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // (boilerplate databinding code omitted here)
    sharedPref = getPreferences(Context.MODE_PRIVATE)
}

private fun loadSN() {
    if (sharedPref.contains(PREFERENCE_SN)) {
        binding.serialNumberEditText.setText(sharedPref.getString(PREFERENCE_SN, ""))
        Log.d(TAG, "serialNumberEditText is now ${binding.serialNumberEditText.text.toString()}")
    }
}

private fun saveSN() {
    Log.d(TAG, "saveSN: saving ${binding.serialNumberEditText.text.toString()}")
    with (sharedPref.edit()) {
        putString(PREFERENCE_SN, binding.serialNumberEditText.text.toString())
        apply()
    }
}

override fun onStart() {
    super.onStart()
    loadSN()
}
override fun onPause() {
    super.onPause()
    saveSN()
}

override fun onStop() {
    super.onStop()
    saveSN()
}
override fun onResume() {
    super.onResume()
    loadSN()
}
override fun onRestart() {
    super.onRestart()
    loadSN()
}

Пример вывода журнала при переходе к другому приложению и обратно:

[navigate away from app]
D/FooActivity: saveSN: saving ABCDE
[^^ return to app here]
D/FooActivity: serialNumberEditText is now ABCDE
D/FooActivity: savePreference was called, restoring ABCDE
D/FooActivity: serialNumberEditText is now ABCDE
D/FooActivity: savePreference was called, restoring ABCDE
D/FooActivity: serialNumberEditText is now ABCDE
D/FooActivity: savePreference was called, restoring ABCDE

Но это не восстановление ABCDE в поле EditText. Почему?

Я подозреваю, что это как-то связано с кодом привязки данных, который вы пропустили выше. Кстати, ваши перегрузки onStart, onStop и onRestart избыточны, поскольку onPause и onResume всегда вызываются, когда активность теряет/приобретает фокус.

Tenfour04 18.12.2020 15:30
1
1
159
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать viewModel и модуль сохранения состояния, который сделает это за вас. Добавьте это в gradle (уровень приложения):

 dependencies {
        // Java language implementation
        implementation "androidx.savedstate:savedstate:1.0.0"
    
        // Kotlin
        implementation "androidx.savedstate:savedstate-ktx:1.1.0-rc01"
    }

если вы не хотите использовать модуль, вы можете сохранить состояние через:

     @Override
        protected void onSaveInstanceState(@NonNull Bundle outState) {
            outState.putString("edit_text", yourEditTextView.getText().toString());
 super.onSaveInstanceState(outState);
        }

и получить это значение

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState!=null){
            if (savedInstanceState.containsKey("edit_text")){
                String valueForEditText = savedInstanceState.getString("edit_text");
                yourEditTextView.setText(valueForEditText);
            }
        }

Вы не должны сохранять состояние при остановке и паузе, просто определите свой текст редактирования при создании, и Android сделает это за вас.

Если бы у вас был действительно сложный пользовательский интерфейс, я могу понять, почему это может представлять интерес. Тем не менее, я чувствую, что не должна требоваться отдельная ViewModel для поддержки содержимого одного EditText, если пользователь уходит и возвращается обратно. Есть ли более простой способ?

Escher 18.12.2020 14:54

Простым решением было бы @Override onSaveInstanceState() и onRestoreInstanceState внутри вашей активности. Достаточно обрабатывать вызовы onPause и onStop вашей активности. Вызов OnDestroy может поддерживаться таким образом, только если он вызван поворотом экрана. Вы должны использовать общие настройки, только если хотите восстановить состояние EditText после того, как приложение было закрыто пользователем.

Alex Rmcf 18.12.2020 15:31

На самом деле, насколько я помню, достаточно определить ваш текст редактирования как переменную внутри вашего класса активности, и текст внутри него будет автоматически восстановлен после вызовов onPause, onStop системой Android. Но я не могу проверить это прямо сейчас.

Alex Rmcf 18.12.2020 15:35
Ответ принят как подходящий

Фреймворк автоматически сохранит состояние Views (включая EditTexts) в текущем макете, но только пока приложение находится в «фоновом режиме». Это включает в себя, когда приложение уничтожается системой для освобождения памяти, представления будут восстановлены в том виде, в котором они были, но не в том случае, если пользователь смахивает приложение, чтобы явно закрыть его, или если вы удаляете макет, например. переходим к другому Activity. Если вы хотите, чтобы те же данные отображались в следующий раз, когда пользователь откроет этот макет, вам нужно будет сохранить состояние самостоятельно, и это означает что-то вроде использования SharedPreferences.

Судя по вашим логам, восстановление идет нормально? Вы сохраняете ABCDE, вы устанавливаете его на ABCDE в следующий раз, когда вы вернетесь, затем вы проверяете содержимое EditText, и он установлен на ABCDE? Что вы видите вместо этого, и есть ли что-то еще, что устанавливает данные в этом представлении, например привязка данных в XML? Система восстанавливает состояние просмотра до onStart, поэтому ваше восстановление происходит после этого, но если есть какое-либо сохраненное состояние, это должны быть те же данные, которые вы все равно сохранили.

Да, это странно; ясно, несмотря на то, что говорят журналы, я делаю это неправильно, поскольку ABCDE не восстанавливается до EditText на экране. Я использую привязку данных, чтобы упростить обращение к элементам TextView, которые отображают метаданные базы данных. На TextChangedListener есть EditText, но его отключение не влияет на поведение.

Escher 18.12.2020 15:02

Вы можете попробовать добавить точку останова в метод setText EditText (например, в классе EditText нажмите ctrl+щелчок по имени метода, чтобы попасть туда), а затем отлаживать приложение — надеюсь, оно будет останавливаться каждый раз, когда что-то изменяет содержимое, и вы можете посмотреть на стек вызовов слева, чтобы увидеть, что установлено не так. Ваш код, похоже, работает нормально, поэтому должно быть что-то еще, появившееся позже и снова изменившее его?

cactustictacs 18.12.2020 15:24

В документации прямо указано, что состояние пользовательского интерфейса не сохраняется автоматически, когда приложение переводится в фоновый режим или уничтожается для освобождения памяти: developer.android.com/topic/libraries/architecture/…

Tenfour04 18.12.2020 15:26

Это для Views: developer.android.com/guide/components/activities/… «По умолчанию система использует состояние экземпляра Bundle для сохранения информации о каждом объекте View в вашем макете активности (например, текстовое значение введен в виджет EditText). Таким образом, если ваш экземпляр активности уничтожается и создается заново, состояние макета восстанавливается до его предыдущего состояния без необходимости кода». Вы можете попробовать это с включенным Don't keep activities и EditText в макете. Документация Android дикая, я все еще видел скриншоты Holo

cactustictacs 18.12.2020 15:43

@cactustictacs Запускать отладчик странно. Он работает с точкой останова отладчика, которая вызывает loadSN 3 раза (onRestart, onStart, onResume), но когда я выключаю отладчик, он больше не работает. Он работает последовательно, когда я использую Handler, чтобы применить задержку к вызову setText внутри loadSN. Очевидно, что в моем коде происходит какое-то странное состояние гонки, поэтому я приму ваш ответ как в целом правильный способ сделать это. Спасибо!

Escher 18.12.2020 17:02

Другие вопросы по теме