Como salvar o estado com fragmentos

Várias operações do sistema Android podem afetar o estado do fragmento. Para garantir que o estado do usuário seja salvo, o framework do Android salva e restaura automaticamente os fragmentos e a pilha de retorno. Portanto, você precisa garantir que todos os dados do seu fragmento também sejam salvos e restaurados.

A tabela a seguir descreve as operações que fazem com que o fragmento perca o estado e se os vários tipos de estado persistem com essas mudanças. Os tipos de estado mencionados na tabela são os seguintes:

  • Variáveis: variáveis locais no fragmento.
  • Estado da visualização: qualquer dado pertencente a uma ou mais visualizações no fragmento.
  • SavedState: dados inerentes a essa instância de fragmento que precisam ser salvos em onSaveInstanceState().
  • NonConfig: dados extraídos de uma fonte externa, como um servidor ou repositório local, ou dados criados pelo usuário que são enviados a um servidor após a confirmação.

Muitas vezes, as variáveis são tratadas da mesma forma que SavedState, mas a tabela a seguir distingue entre os dois para demonstrar o efeito das várias operações em cada um.

Operação Variáveis Estado da visualização SavedState NonConfig
Adicionado à pilha de retorno x
Mudança de configuração x
Interrupção/recriação de processos x ✓*
Removido e não adicionado à backstack x x x x
Host concluído x x x x

* O estado NonConfig pode ser retido durante o encerramento do processo usando o módulo Saved State para ViewModel.

Tabela 1: várias operações destrutivas de fragmentos e os efeitos que elas têm em diferentes tipos de estado.

Vejamos um exemplo específico. Imagine uma tela que gera uma string aleatória, exibindo-a em uma TextView e oferecendo uma opção para editar a string antes de enviá-la a um amigo:

App gerador de texto aleatório que demonstra vários
            tipos de estado
Figura 1. App gerador de texto aleatório que demonstra vários tipos de estado.

Para esse exemplo, suponha que, quando o usuário pressionar o botão de edição, o app exibirá uma visualização EditText, em que o usuário poderá editar a mensagem. Se o usuário clicar em CANCELAR, a visualização EditText será removida, e a visibilidade será definida como View.GONE. Essa tela pode exigir o gerenciamento de quatro partes de dados para garantir uma experiência perfeita:

Dados Tipo Tipo de estado Descrição
seed Long NonConfig Seed usada para gerar aleatoriamente uma nova boa ação. Gerada quando o ViewModel é criado.
randomGoodDeed String SavedState + variável Gerado quando o fragmento é criado pela primeira vez. A randomGoodDeed é salva para garantir que os usuários tenham acesso à mesma boa ação aleatória até mesmo após o encerramento e a recriação do processo.
isEditing Boolean SavedState + variável Sinalização booleana definida como true quando o usuário começa a editar. isEditing é salvo para garantir que a parte de edição da tela permaneça visível quando o fragmento é recriado.
Texto editado Editable Estado da visualização (propriedade de EditText) O texto editado na visualização EditText. A EditText salva esse texto para garantir que as mudanças em andamento do usuário não sejam perdidas.

Tabela 2: determina que o app gerador de texto aleatório precisa fazer o gerenciamento.

Nas seções a seguir, descrevemos como gerenciar corretamente o estado dos seus dados usando operações destrutivas.

Estado da visualização

As visualizações são responsáveis por gerenciar os próprios estados. Por exemplo, quando uma visualização aceita entrada do usuário, é responsabilidade dela salvar e restaurar essa entrada para processar as mudanças de configuração. Todas as visualizações fornecidas pelo framework do Android têm a própria implementação de onSaveInstanceState() e onRestoreInstanceState(). Assim, você não precisa gerenciar o estado da visualização no seu fragmento.

Por exemplo, no cenário anterior, a string editada é mantida em um EditText. Um EditText sabe o valor do texto que está sendo exibido, bem como outros detalhes, como o início e o fim de qualquer texto selecionado.

Uma visualização precisa de um ID para reter o estado. Esse ID precisa ser exclusivo no fragmento e na hierarquia da visualização. Visualizações sem ID não podem reter o estado.

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

Como mencionado na tabela 1, as visualizações salvam e restauram ViewState usando todas as operações que não removem o fragmento nem destroem o host.

SavedState

Seu fragmento é responsável por gerenciar pequenas quantidades de estado dinâmico que são essenciais para as funções do fragmento. É possível reter dados facilmente serializados usando Fragment.onSaveInstanceState(Bundle). De modo semelhante a Activity.onSaveInstanceState(Bundle), os dados que você coloca no pacote são retidos por mudanças de configuração e processam o encerramento e a recriação, estando disponíveis nos métodos onCreate(Bundle), onCreateView(LayoutInflater, ViewGroup, Bundle) e onViewCreated(View, Bundle) do fragmento.

Continuando com o exemplo anterior, randomGoodDeed é a ação exibida para o usuário, enquanto isEditing é uma sinalização para determinar se o fragmento exibe ou oculta o EditText. Esse estado salvo precisa ser mantido usando onSaveInstanceState(Bundle), conforme mostrado no exemplo a seguir:

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 restaurar o estado em onCreate(Bundle), recupere o valor armazenado do pacote:

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 mencionado na tabela 1, as variáveis são retidas quando o fragmento é colocado na pilha de retorno. Tratá-las como estado salvo garante que elas persistam por todas as operações destrutivas.

NonConfig

Os dados de NonConfig precisam ser colocados fora do seu fragmento, como em um ViewModel. No exemplo anterior, seed (nosso estado NonConfig) é gerado no ViewModel. A lógica para manter o estado pertence ao 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;
    }
}

A classe ViewModel permite inerentemente que os dados sobrevivam a mudanças de configuração, como rotações de tela, e permaneçam na memória quando o fragmento é colocado na pilha de retorno. Após o encerramento e a recriação do processo, o ViewModel é recriado, e uma nova seed é gerada. A adição de um módulo SavedState ao ViewModel permite que o ViewModel retenha o estado simples após o encerramento e a recriação do processo.

Outros recursos

Para ver mais informações sobre como gerenciar o estado de fragmentos, consulte os recursos a seguir.

Codelabs

Guias