Salvando dados temporários
Imagine que você está digitando um número importante no programa SimplestKeypad e antes de terminar o que estava fazendo você é interrompido, talvez por uma ligação telefonica. Depois disso, você desliga o telefone e consequentemente o programa também é finalizado. O que deveria acontecer na próxima vez que você rodar SimplestKeypad ? Os números previamente digitados deveriam ser descartados? Ou o programa deveria reiniciar de onde parou mantendo o estado em que foi abandonado? Obviamente, isto não importa em um simples programa de demonstração como SimplestKeypad mas, em geral, os usuários esperam que os aplicativos para dispositivos móveis lembrem exatamente o que eles estavam fazendo desde a última vez que o programa foi utilizado. Por esta razão, a classe Application suporta duas funcionalidades que ajudam o programa a salvar e restaurar dados:
- A propriedade Properties da classe Application é um dicionário de chaves do tipo string e items to tipo object. O conteudo deste dicionário é salvo automaticamente antes da aplicação ser finalizada e todo o conteúdo salvo se torna disponível na próxima vez que a aplicação roda.
- A classe Application implementa três metodos virtuais protegidos de nome OnStart, OnSleep, e OnResume, e a classe App gerada pelo modelo do Xamarin.Forms sobrepõe/sobre-escreve estes métodos. Eles ajudam um programa a gerenciar o que conhecemos como eventos de ciclo de vida da aplicação.
Para utilizar estas funcionalidades você precisa identificar quais informações do programa precisam ser salvos e, desta forma, poder restaurar o seu estado depois de terminado e reiniciado. Em geral, esta é uma combinação de configurações da aplicação – como cores e tamanhos de fontes de texto que o usuário possa ter escolhido – e dados temporários, como parte de dados já registrados nos campos.
As configurações geralmente são pertinentes a aplicação como um todo enquanto que dados temporários são únicos para cada página ou tela da aplicação. Se cada item destes dados se referem a uma entrada no dicionário Properties, cada item precisará ter uma chave. Entretanto, se um programa precisa salvar um arquivo de tamanho grande, por exemplo um documento gerado em processador de texto Word, ele não deveria utilizar o dicionário Properties, mas sim utilizar o acesso a plataforma do sistema de arquivos diretamente. (Este é um assunto a ser tratado num capítulo posterior.)
O programa SimplestKeypad precisa salvar apenas um único item de dados temporários e a chave displayLabelText no dicionário parece suficiente para esta necessidade. Também é possível que um programa use o dicionário Properties para salvar e recuperar dados sem envolver os eventos de ciclo de vida da aplicação. Por exemplo, o programa SimplestKeypad sabe exatamente quando a propriedade Text do displayLabel é modificada. Isto ocorre apenas nos dois gerenciadores de eventos Clicked para as teclas numéricas e a tecla de apagar.
Estes dois gerenciadores de eventos poderiam simplesmente armazenar os novos valores no dicionário Properties. Mas espere: Properties é uma propriedade da classe Application. Precisamos salvar a instância da classe App para que o código em SimplestKeypadPage possa acessar o dicionário? Não, isto não é necessário. A classe Application implementa uma propriedade estática chamada Current que retorna qual é a instância atual da aplicação. Para armazenar a propriedade Text do Label no dicionário, basta adicionar as seguintes linhas de código ao final do gerenciador de eventos Clicked no programa SimplestKeypad:
Application.Current.Properties["displayLabelText"] = displayLabel.Text;
Não se preocupe caso a chave displayLabelText ainda não exista no dicionário: O dicionário Properties implementa uma interface genérica IDictionary, a qual define explicitamente que o indexador substituia o item anterior caso a chave já exista ou adicione um novo item no dicionário se a chave ainda não existir. Este comportamento é exatamente o que você esperava neste caso. O construtor SimplestKeypadPage pode então concluir seu trabalho inicializando a propriedade Text do Label com o seguinte código que recupera o item do dicionário:
Isto é tudo que a sua aplicação precisa fazer: salvar a informação no dicionário Properties e recuperá-la. O Xamarin.Forms é que fica responsável pelo trabalho de salvar e carregar os conteúdos do dicionário no sistema de armazenamento da aplicação para cada plataforma específica. Em geral, no entanto, é melhor para uma aplicação interagir com o dicionário Properties de uma forma mais estruturada, e é aqui onde os eventos de ciclo de vida da aplicação entram em cena. Estes são os três métodos que aparecem na classe App criada pelo modelo Xamarin.Forms:
O mais importante é a chamada do evento OnSleep. Em geral, uma aplicação entra em modo suspenso quando ela perde o comando da tela e se torna inativa (salvo qualquer execução de tarefa ou serviço em segundo plano que ela possa ter iniciado).
Estando no modo de suspensão, a aplicação pode ser reiniciada (sinalizada por uma chamada OnResume) ou finalizada. Mas, existe um ponto importante: Depois de uma chamada OnSleep não existe nenhuma notificação adicional para informar que a aplicação foi finalizada. A chamada OnSleep é o mais próximo possível que você pode chegar de uma notificação de finalização e ela sempre vai ter precedencia sobre a finalização. Por exemplo, se a aplicação está rodando e o usuário desliga o telefone, a aplicação recebe uma chamada OnSleep uma vez que o telefone está sendo desligado.
Na realidade, existem algumas exceções a esta regra onde uma chamada OnSleep sempre tem precedencia sobre a finalização: um programa que trava não irá receber uma chamada OnSleep antes de travar, mas você provavelmente já teria esta expectativa. Por outro lado, aqui está um caso para o qual você não anteciparia esta situação: Quando você está depurando uma aplicação Xamarin.Forms e utiliza o Visual Studio ou Xamarin Studio, o programa é finalizado sem uma chamada antecipada de OnSleep. Isto significa que quando você está depurando código que utiliza eventos de ciclo de vida da aplicação, você deve ter o hábito de usar o próprio telefone para suspender o programa, para reiniciar o programa e para finalizá-lo.
Quando a sua aplicação Xamarin.Forms está rodando, a forma mais fácil de disparar uma chamada OnSleep num simulador de telefone é pressionando o botão Home. Depois você pode trazer o programa novamente para primeiro plano e disparar uma chamada OnResume selecionando a aplicação via home menu(em dispositivos iOS e Android) ou pressionando o botão Back (em dispositivos Android e Windows Phone).
Se o seu programa Xamarin.Forms está rodando e você chama o seletor de aplicações do telefone – pressionando o botão Home duas vezes em dispositivos iOS, pressionando o botão Multitask em dispositivos Android (ou mantendo o botão Home pressionado em dispositivos Android mais antigos), ou mantendo o botão Back pressionado no Windows Phone – a aplicação recebe uma chamada OnSleep. Depois disto se você selecionar o programa, a aplicação recebe uma chamada OnResume pois irá resumir a sua execução. Ao invés disto, caso a aplicação seja terminada – arrastando a imagem do aplicativo para cima até “jogar fora” nos dispositivos iOS ou pressionando o botão X que aparece no canto superior direito na imagem da aplicação nos dispositivos Android ou no Windows Phone – o programa para de executar sem receber nenhuma notificação adicional.
Assim, aqui segue uma regra básica: não importa quando a sua aplicação recebe uma chamada OnSleep, você tem que garantir que o dicionário Properties tenha todas as informações que você gostaria de salvar a respeito da aplicação.
Se você está utilizando eventos de ciclo de vida da aplicação apenas para salvar e restaurar dados do programa, você não precisa controlar o método OnResume. Quando o seu programa recebe uma chamada OnResume, o sistema operacional restaura o conteúdo e estado da aplicação automaticamente. Caso você queira, você pode utilizar OnResume como uma oportunidade para limpar o dicionário Properties porque você tem certeza de que haverá outra chamada OnSleep antes do programa ser finalizado. Por outro lado, caso o seu programa tenha estabelecido uma conexão com um serviço web – ou esteja em processo para estabelecer tal conexão – você poderia utilizar OnResume para restaurar a conexão. Talvez o tempo limite de espera da conexão tenha expirado durante o período que o programa ficou inativo. Ou pode ser que novos dados estejam disponíveis.
Você tem algumas flexibilidades quando você restaura dados do dicionário Properties para sua aplicação quando o programa começa a rodar. Quando um programa Xamarin.Forms é inicializado, a primeira oportunidade que você tem para executar um código na biblioteca de classes portáveis é no construtor da classe App. Neste momento o dicionário Properties já foi preenchido com dados salvos que vieram da plataforma de armazenamento específica. O próximo código executado é geralmente o construtor da página inicial da sua aplicação que é instanciado via construtor de App. A chamada OnStart em Application(e App) acontece em seguida, e então um método substituível chamado OnAppearing é chamado na classe da página. Você pode recuperar os dados a qualquer momento durante este processo de inicialização.
Os dados que uma aplicação precisa salvar geralmente estão na classe de uma página, mas a substituição/sobreposição de OnSleep está na classe App. Então de algum jeito as classes página e App tem que se comunicar. Uma forma que funciona bem para uma aplicação que tem apenas uma página – de fato, a classe Application tem uma propriedade estática chamada MainPage a qual é definida no construtor App onde o método OnSleep pode obter acesso aquela página – mas isto não vai funcionar muito bem para uma aplicação com várias páginas.
Veja abaixo uma forma um pouco diferente: defina todos os dados que precisam ser salvos como propriedades públicas na classe App, por exemplo:
A classe (ou classes) de página podem então atribuir e recuperar valores destas propriedades quando for conveniente. A classe App consegue restaurar este tipo de propriedade do dicionário Properties no seu construtor e antes de instanciar a página consegue armazenar as propriedades no dicionário Properties no seu método sobreposto OnSleep.
Esta é a forma empregada no projeto PersistentKeypad. Este programa é idêntico ao programa Simplest-Keypad exceto que ele adiciona código para salvar e restaurar as informações do teclado. Aqui está a classe App que implementa a propriedade DisplayLabelText que é salva no método sobreposto OnSleep e carregada no construtor de App:
Para evitar erros de ortografia, a classe App declara a string da chave dicionário como uma constante. Ela tem o mesmo nome da propriedade, mas começa com letra minúscula. Note que a propriedade DisplayLabelText é declarada antes de instanciar PersistentKeypadPage então ela já está disponível no construtor PersistentKeypadPage.
Uma aplicação contendo vários itens poderia consolidá-los em uma classe de nome AppSettings (por exemplo), serializar esta classe em uma string XML ou JSON, e então salvar a string no dicionário. A classe PersistentKeypadPage acessa a propriedade DisplayLabelText no seu construtor e define a propriedade nos seus dois gerenciadores de eventos:
Durante a execução de testes de programas que usam o dicionário Properties e eventos de ciclo de vida de aplicação você poderia precisar, ocasionalmente, desinstalar o programa do telefone ou simulador. Desinstalar um programa do dispositivo também remove qualquer dado armazenado para aquele programa, então da próxima vez que o programa for transmitido/instalado do Visual Studio ou Xamarin Studio para/no o aparelho ou simulador, o programa vai ter um dicionário vazio, como se estivesse rodando pela primeira vez.