Cómo guardar un estado con fragmentos

Varias operaciones del sistema Android pueden afectar el estado de tu fragmento. Para garantizar que se guarde el estado del usuario, el framework de Android guarda y restablece automáticamente los fragmentos y la pila de actividades. Por lo tanto, debes asegurarte de guardar y restablecer también todos los datos de tu fragmento.

En la siguiente tabla, se describen las operaciones que hacen que el fragmento pierda su estado, junto con la posibilidad de que los diferentes tipos de estado se conserven a través de esos cambios. Los tipos de estado que se mencionan en la tabla son los siguientes:

  • Variables: Variables locales en el fragmento
  • View State: Cualquier dato que pertenezca a una o más vistas del fragmento
  • SavedState: Datos inherentes a esta instancia del fragmento que se deben guardar en onSaveInstanceState()
  • NonConfig: Datos extraídos de una fuente externa, como un servidor o repositorio local, o datos creados por el usuario que se envían al servidor una vez confirmados

A menudo, las Variables se tratan de la misma manera que el SavedState, pero la tabla siguiente distingue entre los dos para mostrar el efecto de las distintas operaciones en cada uno.

Operación Variables View State SavedState NonConfig
Agregado a la pila de actividades x
Cambio de configuración x
Cierre o recreación del proceso x ✓*
Quitado, no agregado a la pila de actividades x x x x
Host terminado x x x x

* Se puede retener el estado NonConfig luego del cierre del proceso mediante el módulo de estado guardado para ViewModel.

Tabla 1: Varias operaciones destructivas de fragmentos y los efectos que tienen en diferentes tipos de estados

Veamos un ejemplo específico. Considera una pantalla que genera una string aleatoria, la muestra en una TextView y proporciona la opción de editar la string antes de enviarla a un amigo:

App de generación de textos aleatorios que demuestra varios tipos de estado
Figura 1: App de generación de textos aleatorios que demuestra varios tipos de estado

Para este ejemplo, supongamos que una vez que el usuario presiona el botón de edición, la app muestra una vista de EditText en la que el usuario puede editar el mensaje. Si este hace clic en CANCELAR, la vista de EditText se debe borrar y su visibilidad se debe establecer en View.GONE. Tal pantalla podría requerir la administración de cuatro datos para garantizar una experiencia sin interrupciones:

Datos Tipo Tipo de estado Descripción
seed Long NonConfig Seed utilizada para generar un nuevo random good deed. Generada cuando se crea el ViewModel.
randomGoodDeed String SavedState + Variable Generado cuando se crea el fragmento por primera vez. randomGoodDeed se guarda para garantizar que los usuarios vean el mismo random good deed, incluso después del cierre del proceso y de su recreación.
isEditing Boolean SavedState + Variable Marca booleana establecida en true cuando el usuario comienza a editar. Se guarda isEditing para garantizar que la parte de edición de la pantalla permanezca visible cuando se vuelva a crear el fragmento.
Texto editado Editable View State (propiedad de EditText) El texto editado en la vista de EditText. "EditText" guarda este texto para garantizar que no se pierda la edición en curso del usuario.

Tabla 2: Estados que debe administrar la app de generación de textos aleatorios

En las siguientes secciones, se describe cómo administrar adecuadamente el estado de tus datos mediante operaciones destructivas.

Estado de vistas

Las vistas se encargan de administrar su propio estado. Por ejemplo, cuando una vista acepta la entrada del usuario, es su responsabilidad guardar y restablecer esa entrada para controlar los cambios de configuración. Todas las vistas proporcionadas por el framework de Android tienen su propia implementación de onSaveInstanceState() y onRestoreInstanceState(), por lo que no necesitas administrar el estado de la vista en tu fragmento.

Por ejemplo, en la situación anterior, la string editada se conserva en un EditText. Un EditText reconoce el valor del texto que muestra, así como otros detalles, como el principio y el final del texto seleccionado.

Una vista necesita un ID para retener su estado. Ese ID debe ser único dentro del fragmento y de su jerarquía de vistas. Las vistas que no tienen un ID no pueden retener su estado.

<EditText
    android:id="@+id/good_deed_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Como se mencionó en la Tabla 1, las vistas guardan y restablecen su ViewState a través de todas las operaciones que no quitan el fragmento ni destruyen el host.

SavedState

Tu fragmento se encarga de administrar pequeñas cantidades de estado dinámico que son fundamentales para su funcionamiento. Puedes retener datos serializados con facilidad mediante Fragment.onSaveInstanceState(Bundle). Similar a Activity.onSaveInstanceState(Bundle), los datos que colocas en el paquete se retienen a través de cambios de configuración y del cierre y la recreación de los procesos, y están disponibles en los métodos onCreate(Bundle), onCreateView(LayoutInflater, ViewGroup, Bundle) y onViewCreated(View, Bundle) de tu fragmento.

Sugerencia: Cuando usas un ViewModel, puedes guardar el estado directamente en el ViewModel mediante un SavedStateHandle. Para obtener más información, consulta el módulo de estado guardado para ViewModel.

Siguiendo con el ejemplo anterior, randomGoodDeed es la acción que se muestra al usuario y isEditing es una marca para determinar si el fragmento muestra u oculta el EditText. Este estado guardado debe conservarse por medio de onSaveInstanceState(Bundle), como se muestra en el siguiente ejemplo:

Kotlin

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putBoolean(IS_EDITING_KEY, isEditing)
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed)
}

Java

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(IS_EDITING_KEY, isEditing);
    outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed);
}

Para restablecer el estado de onCreate(Bundle), recupera el valor almacenado del paquete:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    isEditing = savedInstanceState?.getBoolean(IS_EDITING_KEY, false)
    randomGoodDeed = savedInstanceState?.getString(RANDOM_GOOD_DEED_KEY)
            ?: viewModel.generateRandomGoodDeed()
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        isEditing = savedInstanceState.getBoolean(IS_EDITING_KEY, false);
        randomGoodDeed = savedInstanceState.getString(RANDOM_GOOD_DEED_KEY);
    } else {
        randomGoodDeed = viewModel.generateRandomGoodDeed();
    }
}

Como se mencionó en la Tabla 1, ten en cuenta que las variables se retienen cuando el fragmento se coloca en la pila de actividades. Tratarlas como estados guardados garantiza que se conserven a lo largo de todas las operaciones destructivas.

NonConfig

Los datos de NonConfig deben colocarse fuera de tu fragmento, como en un ViewModel. En el ejemplo anterior, seed (nuestro estado de NonConfig) se genera en el ViewModel. La lógica para mantener su estado pertenece al ViewModel.

Kotlin

public class RandomGoodDeedViewModel : ViewModel() {
    private val seed = ... // Generate the seed

    private fun generateRandomGoodDeed(): String {
        val goodDeed = ... // Generate a random good deed using the seed
        return goodDeed
    }
}

Java

public class RandomGoodDeedViewModel extends ViewModel {
    private Long seed = ... // Generate the seed

    private String generateRandomGoodDeed() {
        String goodDeed = ... // Generate a random good deed using the seed
        return goodDeed;
    }
}

La clase ViewModel permite de forma inherente que los datos sobrevivan a los cambios de configuración, como las rotaciones de pantalla, y que permanezcan en la memoria cuando el fragmento se coloca en la pila de actividades. Después del cierre y de la recreación del proceso, se recrea el ViewModel y se genera una nueva seed. Agregar un módulo de SavedState a tu ViewModel permite que el ViewModel retenga un estado simple a través del cierre y la recreación del proceso.

Recursos adicionales

Si deseas obtener más información para administrar el estado de los fragmentos, consulta los siguientes recursos adicionales:

Codelabs

Guías