Salvando estados em aplicativos Android

Hoje eu queria compartilhar com você outra abordagem para manter o estado ao desenvolver aplicativos para Android. Não é segredo que nossa aplicação em segundo plano possa ser eliminada a qualquer momento e esse problema se torna mais urgente com a introdução de uma economia agressiva de energia - oi, Oreo . Além disso, ninguém cancelou a alteração de configuração no telefone: orientação, alteração de idioma etc. E, para abrir o aplicativo em segundo plano e exibir a interface no último estado, precisamos cuidar de salvá-lo. Oh, isso em SaveInstanceState .

onSaveInstanceState

Quanta dor ele nos trouxe.

Vou dar exemplos mais tarde usando o Clean Achitecture e o Dagger2 , então esteja preparado para isso :)

A questão da manutenção do estado dependendo das tarefas pode ser resolvida de várias maneiras:

  1. Salve os dados primários no onSaveInstanceState do host (Atividade, Fragmento) - como o identificador da página, o usuário e o que for. O que precisamos para aquisição inicial de dados e exibição de página.
  2. Salve os dados recebidos no programa interativo no repositório (SharedPreference, Database.
  3. Use fragmentos de retina para salvar e restaurar dados ao recriar atividades.

Mas e se precisarmos restaurar o estado da interface do usuário, bem como a reação atual da interface à ação do usuário? Para simplificar, vamos considerar a solução para esse problema usando um exemplo real. Temos uma página de login - o usuário digita seus dados, clica no botão e recebe uma chamada. Nossa aplicação entra em segundo plano. O sistema o mata. Parece assustador, não é?)

O usuário retorna ao aplicativo e o que ele deve ver? No mínimo, continuando a operação de login e mostrando o progresso. Se o aplicativo conseguiu efetuar login antes de chamar o método onDestroy do host, o usuário verá a navegação na tela inicial do aplicativo. Esse comportamento pode ser facilmente resolvido usando a máquina de estado. Muito bom relatório da Yandex . No mesmo artigo, tentarei compartilhar meus pensamentos mastigados sobre este relatório.

Agora um pequeno código:

Basestate

public interface BaseState<VIEW extends BaseView, OWNER extends BaseOwner> extends Parcelable{ /** * Get name * * @return name */ @NonNull String getName(); /** * Enter to state * * @param aView view */ void onEnter(@NonNull VIEW aView); /** * Exit from state */ void onExit(); /** * Return to next state */ void forward(); /** * Return to previous state */ void back(); /** * Invalidate view * * @param aView view */ void invalidateView(@NonNull VIEW aView); /** * Get owner * * @return owner */ @NonNull OWNER getOwner(); /** * Set owner * * @param aOwner owner */ void setOwner(@NonNull OWNER aOwner); } 

Governador de base

 public interface BaseOwner<VIEW extends BaseView, STATE extends BaseState> extends BasePresenter<VIEW>{ /** * Set state * * @param aState state */ void setState(@NonNull STATE aState); } 

BasestateImpl

 public abstract class BaseStateImpl<VIEW extends BaseView, OWNER extends BaseOwner> implements BaseState<VIEW, OWNER>{ private OWNER mOwner; @NonNull @Override public String getName(){ return getClass().getName(); } @Override public void onEnter(@NonNull final VIEW aView){ Timber.d( getName()+" onEnter"); //depend from realization } @Override public void onExit(){ Timber.d(getName()+" onExit"); //depend from realization } @Override public void forward(){ Timber.d(getName()+" forward"); onExit(); //depend from realization } @Override public void back(){ Timber.d(getName()+" back"); onExit(); //depend from realization } @Override public void invalidateView(@NonNull final VIEW aView){ Timber.d(getName()+" invalidateView"); //depend from realization } @NonNull @Override public OWNER getOwner(){ return mOwner; } @Override public void setOwner(@NonNull final OWNER aOwner){ mOwner = aOwner; } 

No nosso caso, o proprietário do estado será um apresentador.

Observando a página de login, três estados exclusivos podem ser distinguidos:

LoginInitState , LoginProgressingState , LoginCompleteState .

Então, agora considere o que acontece nesses estados.

LoginInitState , temos validação de campos e, em caso de validação bem-sucedida, o botão de login se torna ativo.

Uma solicitação de logon é feita no LoginProgressingState , um token é salvo, solicitações adicionais são feitas para iniciar a atividade principal do aplicativo.

LoginCompleteState navega para a tela principal do aplicativo.

Condicionalmente, a transição entre estados pode ser exibida no seguinte diagrama:

Diagrama de status de login

O estado LoginProgressingState será encerrado se a operação de login for bem-sucedida no estado LoginCompleteState e se LoginInitState falhar . Assim, quando nossa visão se destaca, temos um estado completamente determinístico do apresentador. Devemos salvar esse estado usando o mecanismo padrão do Android onSaveInstanceState . Para fazer isso, todos os estados de login devem implementar a interface Parcelable . Portanto, estamos expandindo nossa interface base BaseState .

Em seguida, temos uma pergunta: como encaminhar esse estado do apresentador para o nosso host? A maneira mais fácil é solicitar ao apresentador dados do host, mas, do ponto de vista da arquitetura, isso não parece muito bom. E assim reter fragmentos vem em nosso auxílio. Podemos criar uma interface para o cache e implementá-lo neste fragmento:

 public interface Cache{ /** * Save cache data * * @param aData data */ void saveCacheData(@Nullable Parcelable aData); @Nullable Parcelable getCacheData(); /** * Check that cache exist * * @return true if cache exist */ boolean isCacheExist(); } 

Em seguida, injetamos o fragmento de cache no construtor do interator, como Cache. Adicionamos métodos no integrador para obter e salvar o estado no cache. Agora, com todas as alterações no estado do apresentador, podemos salvar o estado no interator, e o interator, por sua vez, salva no cache. Tudo se torna muito lógico. Quando o host é carregado pela primeira vez, o apresentador recebe o estado do interator, que por sua vez recebe dados do cache. É assim que o método de alteração de estado no apresentador se parece:

 @Override public void setState(@NonNull final LoginBaseState aState){ mState.onExit(); mState = aState; clearDisposables(); mState.setOwner(this); mState.onEnter(getView()); mInteractor.setState(mState); } 

Eu gostaria de observar esse ponto - salvar dados através do cache pode ser feito para qualquer dado, não apenas para o estado. Pode ser necessário criar seu próprio cache de snippet exclusivo para armazenar os dados atuais. Este artigo descreve uma abordagem geral. Gostaria também de observar que a situação em questão é muito exagerada. Na vida você tem que resolver problemas muito mais complicados. Por exemplo, em nossa aplicação, foram combinadas três páginas: login, registro, recuperação de senha. Nesse caso, o diagrama de estado era o seguinte:

Diagrama de estado em um projeto real

Como resultado, usando o padrão e a abordagem de estado descritos no artigo, conseguimos tornar o código mais legível e sustentável. E o importante é restaurar o estado atual do aplicativo.

O código completo pode ser visualizado no repositório .

Source: https://habr.com/ru/post/pt420023/


All Articles