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 .

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:
- 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.
- Salve os dados recebidos no programa interativo no repositório (SharedPreference, Database.
- 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:
Basestatepublic interface BaseState<VIEW extends BaseView, OWNER extends BaseOwner> extends Parcelable{ @NonNull String getName(); void onEnter(@NonNull VIEW aView); void onExit(); void forward(); void back(); void invalidateView(@NonNull VIEW aView); @NonNull OWNER getOwner(); void setOwner(@NonNull OWNER aOwner); }
Governador de base public interface BaseOwner<VIEW extends BaseView, STATE extends BaseState> extends BasePresenter<VIEW>{ 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");
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:

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{ void saveCacheData(@Nullable Parcelable aData); @Nullable Parcelable getCacheData(); 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:

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 .