Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter: Persistência de Dados com SharedPreferences e Secure Storage

Flutter: Persistência de Dados com SharedPreferences e Secure Storage

Utilizando SharedPreferences - Apresentação

Apresentando o instrutor e o curso

Olá! Gostaríamos de dar as boas-vindas a mais um curso de Flutter aqui na Alura. Meu nome é William Bezerra e serei o instrutor ao longo de toda essa jornada.

Audiodescrição: William é um homem pardo, usa óculos e tem barba preta.

Introduzindo o aplicativo RunCycles e suas funcionalidades

Ao longo deste curso, nós iremos construir juntos o aplicativo RunCycles. Na verdade, não construiremos o aplicativo inteiro, mas adicionaremos algumas funcionalidades relacionadas à persistência de dados. Utilizaremos o banco de dados do dispositivo do usuário para salvar informações que impactam diretamente na experiência dele ao utilizar o aplicativo.

Inicialmente, salvaremos o login do usuário. Ou seja, uma vez que o usuário entra na aplicação, precisamos armazenar, por exemplo, que ele está logado. Assim, quando ele acessar novamente, não precisará informar essas informações, pois já teremos esses dados salvos e o redirecionaremos para a tela inicial (home).

Explicando a segurança e preferências do usuário

Na aplicação, o usuário conseguirá entrar e ser redirecionado para a tela inicial. Ao reiniciarmos o aplicativo, haverá uma tela de splash, que é uma tela inicial responsável por verificar se o usuário está logado ou não, redirecionando-o para a tela inicial ou para a tela de login, tudo isso com segurança.

Vamos utilizar o Flutter Secure Storage para garantir que esses dados, mesmo que salvos nos dispositivos dos usuários, só possam ser acessados utilizando a nossa aplicação. Isso é garantido por uma espécie de criptografia muito bem estruturada pelo próprio dispositivo, assegurando que esses dados estejam salvos de forma segura.

Além disso, utilizaremos o Shared Preference para salvar as preferências dos usuários. Por exemplo, podemos salvar se o tema do aplicativo será claro ou escuro. Assim, ao trocar o tema para claro e reiniciar o aplicativo, ao fechá-lo e abri-lo novamente, o tema permanecerá claro, mantendo as preferências do usuário.

Destacando a importância da persistência de dados

Ao longo de toda a aplicação, trabalharemos com persistência de dados, o que é essencial para o desenvolvimento de aplicativos. Isso é importante tanto para quem está construindo aplicativos quanto para quem já atua no mercado, pois permite implementar funcionalidades específicas e salvar preferências dos usuários. Utilizaremos a estrutura do dispositivo do usuário, aproveitando o espaço de armazenamento para salvar esses dados.

Se você gostou, compartilhe este curso com seus amigos para estudarem juntos. Nos vemos na próxima aula.

Utilizando SharedPreferences - O que é o shared preferences

Introduzindo o cenário e os problemas a serem resolvidos

Para iniciarmos, vamos considerar o seguinte cenário: fomos contratados pela RunCycles para dar continuidade ao aplicativo deles. Nossa tarefa é desenvolver duas funcionalidades específicas dentro da aplicação, pois existem dois problemas ocorrendo atualmente.

O primeiro problema está relacionado à persistência do login. Ao entrar no aplicativo RunCycles, conseguimos fazer o login inserindo e-mail e senha. No entanto, esse login não é persistido, ou seja, ao reiniciar a aplicação, o usuário precisa logar novamente. Isso prejudica a experiência do usuário. Imagine se, toda vez que acessássemos o Instagram, tivéssemos que inserir nossos dados novamente. Seria uma experiência muito ruim.

Além desse problema, há outro relacionado à persistência de tema. Podemos alternar entre o tema claro e o tema escuro, e a aplicação responde adequadamente a essa mudança. No entanto, ao reiniciar o aplicativo, ele não mantém a configuração de tema escolhida, não salvando esse dado.

Abordando a solução com Flutter

Precisamos atuar nesses dois fluxos, salvando esses dados no banco de dados local do aplicativo do usuário, ou seja, precisamos armazenar essas informações na memória.

Como fazemos isso com o Flutter? Existem diversas maneiras de abordar essa questão, desde as mais simples até as mais complexas. Podemos criar banco de dados, fazer integração e toda uma estrutura. No entanto, para o nosso contexto, como estamos lidando com apenas dois dados, vamos começar pela abordagem mais simples possível. Essa abordagem se refere tanto à parte de salvar uma chave e um dado quanto a salvar algo com mais segurança.

Implementando o uso do SharedPreferences

Vamos iniciar pelo que é mais simples dentro desse contexto, que é a função de salvar dados com chave e valor, semelhante a um JSON. Para isso, utilizaremos um package chamado SharedPreferences, que utiliza a estrutura nativa de banco de dados tanto do Android quanto do iOS para salvar esses dados. É isso que começaremos a implementar.

Dentro do VSCode, abriremos o terminal e executaremos o comando para adicionar o pacote necessário ao nosso projeto. Primeiro, vamos adicionar o comando base:

flutter pub add

Em seguida, adicionamos o pacote específico que precisamos:

flutter pub add shared_preferences

Com isso, ele adicionará ao nosso pubspec.yaml essa dependência necessária, que orquestrará o salvamento dos dados, independentemente da plataforma. É como se fosse uma abstração que, no Android, utiliza o próprio SharedPreferences, que é o nome da tecnologia do Android para salvar dados, e no iOS utiliza o NSUserDefaults. Assim, ele trabalha essa estrutura para usar os esquemas nativos específicos para salvar esses dados.

Após adicionar o package à aplicação, o arquivo pubspec.yaml será atualizado para incluir a versão do pacote:

shared_preferences: ^2.5.3

Agora que configuramos o pacote SharedPreferences, podemos prosseguir para a implementação das funcionalidades de persistência de login e tema.

Utilizando SharedPreferences - Utilizando o Shared Preferences

Introduzindo o uso do SharedPreference

Para entendermos o funcionamento do SharedPreference, vamos utilizar a tela inicial do nosso aplicativo, a LoginScreen, e realizar alguns testes para compreender todo esse funcionamento. Basicamente, vamos criar uma instância, por exemplo, finalSharedPreference, que será igual ao SharedPreference. Selecionaremos a opção que já traz o import necessário. Em seguida, faremos um SharedPreference.getInstance, que criará uma instância, um pattern (padrão), para utilizar o Preference, armazenando-a em uma variável.

Vamos começar criando a instância do SharedPreferences:

final sharedPreferences = SharedPreferences.getInstance();

Explorando operações CRUD com SharedPreference

Com essa configuração do SharedPreference, podemos realizar interações. Basicamente, são de três a quatro operações que podemos executar, dependendo de como imaginamos. As operações são: adição (para adicionar um novo dado), exclusão (para excluir um dado), edição (para editar um dado) e get (para obter um dado). Essas são as quatro estruturas que normalmente utilizamos no estilo de CRUD.

Vamos validar como isso seria feito. Começaremos pela criação, com a função voidCreate. Vamos criar algumas funções para testar, ainda que não seja a aplicação da melhor maneira possível, apenas para entender como funciona o SharedPreference. A criação é simples: chamamos a própria instância que temos. Nesse caso, é uma instância futura, então precisamos esperar que aconteça. Teremos, em teoria, um finalPreference igual ao ageSharedPreference. Com isso, conseguimos usar o Preference e até verificar alguns métodos como get e clear. Utilizaremos o método set, que é usado para criar.

Implementando a função de criação

Vamos implementar a função create para adicionar um dado booleano:

void create({required bool isThemeLight}) async {
  final preferences = await sharedPreferences;
  await preferences.setBool('isThemeLight', isThemeLight);
}

O SharedPreference possui cinco tipos para adicionar dados: bool, double, int, string e lista de string. Não mais do que isso. Se quisermos salvar informações mais estruturadas, como um JSON, o SharedPreference não é tão indicado. Existem formas de utilizá-lo nesse contexto, mas ele não é recomendado para aplicações com muitos dados. Para coisas pequenas, sim. Por exemplo, queremos salvar uma informação ou outra, pois ele é mais interessante nesse contexto.

Obtendo dados com SharedPreference

Para obter o dado, o processo é semelhante, mas utilizamos o get. Por exemplo, getInfo ou getTem, que é mais fácil. Também instanciamos o sharedPreference, transformamos a função em assíncrona e utilizamos preference.get. Definimos se é bool, double ou int; neste caso, bool. Passamos o nome da chave que queremos obter, como getBoolTemLight, e ele nos retorna o valor. O getBool não é um future, pois, ao instanciar, ele já traz tudo que está salvo. O ideal é definir um tipo e retornar esse tipo. O retorno será nullable, pois pode não existir um dado salvo. Na maioria das vezes, o primeiro dado não existirá na aplicação.

Vamos implementar a função getTheme para obter o valor salvo:

Future<bool?> getTheme() async {
  final preferences = await sharedPreferences;
  return preferences.getBool('isThemeLight');
}

Editando e excluindo dados

Além disso, como é future, precisamos adicionar o future ao tipo da função. Assim, já temos os dois métodos: um para criar e outro para obter.

Nesse caso, mencionamos que poderiam ser três ou quatro funções, dependendo do contexto. Na verdade, o terceiro seria o editar, mas o editar é basicamente um criar com o mesmo nome. Ou seja, se já definimos inicialmente o stemLight como True e mudamos para False, chamamos esse mesmo método com a mesma variável, salvando por cima. Assim, ele apenas troca o valor dentro da variável. Esse seria o editar.

Por fim, temos o excluir, que é bem simples nessa estrutura. Vamos ter um método void delete, instanciar o preference, que sempre será instanciado, e transformá-lo em async (assíncrono). Basta chamar o preference. Temos o get, o clear, que limpa tudo, e o remove, onde passamos o nome da variável que queremos remover. Nesse caso, queremos remover o stemLight, e ele será removido. É future (futuro) também, então precisamos adicionar um await. Com isso, está removido.

Vamos implementar a função delete para remover o dado:

void delete() async {
  final preferences = await sharedPreferences;
  await preferences.remove('isThemeLight');
}

Testando as funções implementadas

Se quisermos apagar todos os dados salvos na aplicação, chamamos o preference.clear, que limpa tudo e zera novamente. Mas não é o caso aqui, pois só queremos apagar um dado específico. Basicamente, são essas três estruturas. Lembrando que o create pode ser usado tanto para criar novos dados quanto para editar um dado já existente.

Para finalizar, vamos implementar essas funções e testá-las para ver se realmente estão funcionando. Vamos criar dois botões dentro da column, na verdade três, um para cada função, para testar o retorno de cada um. Vamos criar um IconButton que vai salvar. Primeiro, chamaremos o IconButton de salvar, que chamará a função create, recebendo se o tema é light (claro) ou não. Nesse caso, passaremos sempre como true, já que não estamos trabalhando com nenhuma interação ou coisa do tipo. Vamos adicionar um ícone aqui, Icons.save. Perfeito, há um disponível.

Vamos criar os botões para interagir com as funções:

IconButton(
  onPressed: () async {
    await create(isThemeLight: true);
  },
  icon: Icon(Icons.save),
),
IconButton(
  onPressed: () async {
    final result = await getTheme();
    print('RESULT: $result');
  },
  icon: Icon(Icons.search),
),
IconButton(
  onPressed: () {
    delete();
  },
  icon: Icon(Icons.remove),
),

Verificando o funcionamento no console

Pronto, dentro da nossa home, já temos os três botões: salvar, buscar e apagar. Vamos abrir o debug console. A estrutura funciona da seguinte maneira: se tentarmos buscar agora, ele retornará "non-implementation folder". Isso ocorre porque, após instalar o pacote, ainda não reinstalamos o aplicativo. Um ponto importante é que, se adicionarmos o pacote, devemos parar a aplicação e rodá-la novamente, pois ele precisa baixar os dados nativos para a implementação. Vamos rodar e já voltamos.

Pronto, a aplicação executou novamente. Se voltarmos ao VS Code, vamos limpar o console e fechar os arquivos, deixando tudo organizado para acompanhar. Ao clicar em buscar, ele trouxe o resultado como nulo. Isso ocorre porque ainda não temos nada salvo. Agora, ao clicar em salvar, ele executou. Se voltarmos e clicarmos em buscar novamente, ele retornará o resultado como true, pois agora está salvo.

Concluindo a implementação e testes

Se reiniciarmos a aplicação, que é o motivo de toda essa configuração, ao clicar em buscar sem adicionar antes, ao invés de retornar como nulo, ele deveria retornar o resultado, que nesse caso seria true. Vamos verificar. Clicando em buscar, ele retornará um resultado true, indicando que o dado salvo anteriormente ainda está dentro da aplicação. Se clicarmos para deletar, o dado será apagado. Agora, ao buscar, ele deve retornar como nulo. Da mesma forma, se reiniciarmos a aplicação, ao buscar esse dado, ele também estará nulo.

Ou seja, já temos uma estrutura pronta para trabalhar com a questão de salvar dados dentro da aplicação. É exatamente isso que faremos agora: pegar toda essa estrutura que aprendemos e aplicá-la no contexto do aplicativo, inicialmente para salvar o dado da troca de tema.

Sobre o curso Flutter: Persistência de Dados com SharedPreferences e Secure Storage

O curso Flutter: Persistência de Dados com SharedPreferences e Secure Storage possui 72 minutos de vídeos, em um total de 24 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Escolha a duração do seu plano

Conheça os Planos para Empresas