Fala, galera!
Meu nome é Ricarth Lima e hoje eu serei seu instrutor em mais um curso do maravilhoso mundo do Flutter com Firebase!
Autodescrição: Sou um homem negro de pele clara e tenho cabelo crespo bem volumoso. Tenho sobrancelhas grossas, olhos castanhos e boca e nariz largos. Uso óculos retangulares de armação escura e lentes transparentes, e tenho uma barba espessa. Durante a maior parte do curso, estarei com uma camisa azul marinho da Alura. Estou sentado em uma cadeira com encosto estofado preto e, ao fundo, tenho uma parede iluminada pelas cores azul e rosa.
Agora que já nos conhecemos, boas-vindas ao curso Flutter com Firebase: guardando arquivos na nuvem com Storage.
Nesse curso, daremos continuidade ao nosso projeto de lista de compras colaborativas, o Listin. Para quem não o conhece, esse é um projeto que estamos construindo ao longo da formação Flutter com Firebase. Basicamente, ele é um aplicativo onde conseguimos fazer lista nas quais conseguimos adicionar produtos.
As pessoas que estiverem usando esse aplicativo colaborativamente conseguirão saber quais produtos já estão no carrinho e o preço de cada produto. É bem interessante para conseguirmos fazer uma feira junto com outras pessoas.
Neste curso descobriremos como:
É necessário ter uma boa base de Flutter com Dart para este curso, mas, principalmente, que tenham assistido ao Alura+ Como configurar o Firebase no Flutter, onde configuramos um projeto Flutter com o Firebase. Esse Alura+ é obrigatório porque iremos pressupor que vocês fizeram as configurações do Firebase, que servem para qualquer ferramenta do Firebase.
O ideal é que tenham feito a formação A partir do zero: crie projetos em Dart, a linguagem utilizada no Flutter e venham fazer a formação de Firebase.
Dados todos os recados, chegou a hora de irmos para o curso.
Vejo vocês no próximo vídeo!
Agora que conhecemos a proposta do curso e nosso projeto, vamos nos aprofundar em entender o que é o Listin e qual o problema precisamos resolver neste curso.
Caso não tenham intimidade com o Listin, ele é uma lista de compras colaborativa. Na tela de login do aplicativo, podemos fazer o login ou o cadastro.
Como já tenho um cadastro, farei o login.
E-mail: ricarth.limao@gmail.com
Senha: 123321
Ao logarmos com o Firebase Authentication, acessamos a tela inicial, onde temos um trabalho feito com o Cloud Firestore, o banco de dados. Assim conseguimos realmente ter uma lista de compras colaborativa, onde conseguimos contribuir com outras pessoas para fazermos compras juntos.
Podemos criar uma lista de compras, clicando no botão com o sinal de "+", no canto inferior direito da tela, ou podemos entrar na lista já existente, clicando em "Feira de Maio". Ao acessarmos a "Feira de Maio", reparamos que já existe um produto na seção "Produtos Planejados", chamado "Pão doce".
Podemos adicionar outro produto, clicando no botão com sinal de "+" no canto inferior direito. Ao fazermos isso, um formulário aparece na parte inferior da tela, onde podemos preencher as informações de "Nome", "Quantidade" e "Preço". Vamos adicionar, por exemplo, "Pão Francês".
Nome: Pão Francês
Quantidade: 12
Preço: 1
Na parte inferior direita do formulário estão os botões "Cancelar" e "Salvar". Ao clicarmos em "Salvar", voltamos para a tela da lista de compras, onde o "Pão Francês" foi adicionado aos "Produtos Planejados".
Durante as compras, conforme adicionarmos os produtos no carrinho, podemos clicar no botão de check (✓) no lado esquerdo do nome do produto. Dessa forma, ele será movido para o "Produtos Comprados", e isso acontece em tempo real. Todo mundo que estiver usando o aplicativo nessa conta conseguirá observar essas alterações enquanto estiver fazendo a feira.
Isso é bem legal, mas temos uma nova demanda. Ao clicarmos no menu hambúrguer no canto esquerdo da app bar para abrir o drawer, que é um menu lateral na esquerda. Nele temos algumas informações da pessoa usuária logada na barra superior do menu: o nome e o e-mail. Logo abaixo, temos as opções "Remover conta", que é autoexplicativa, e "Sair", para deslogar.
Além disso, no canto superior esquerdo do menu, acima do nome, tem um círculo branco onde deveria ter uma foto de perfil. Entretanto, não damos a opção para pessoa adicionar uma foto de perfil em nenhum lugar do aplicativo.
É justamente o que queremos fazer, mas não podemos simplesmente deixar uma imagem localmente salva no dispositivo. Precisamos que ela esteja na nuvem para, quando acessarem a conta em qualquer outro dispositivo, todas as pessoas logadas consigam ver a foto de perfil.
Podemos pensar em vários locais que poderíamos colocar a opção de adicionar uma foto de perfil, inclusive no próprio menu lateral. Por exemplo, poderíamos colocar um botão dentro do círculo branco.
Porém, esse não é o ideal, porque não teríamos muito espaço para trabalhar. Ele não seria muito bom para configurarmos as opções de listagem e exclusão dessa foto para pessoa usuária. O ideal é criarmos uma nova tela para fazermos a adição e gerenciamento do arquivo de imagem de perfil.
É essa tela que criaremos a seguir.
Agora que entendemos o contexto do problema que precisamos resolver, começaremos a codar para criarmos a tela de configuração da foto de perfil.
Após acessarmos o VS Code, abriremos o Explorador de pastas, clicando no ícone do canto superior direito ou pressionando "Ctrl + Shift + E". Vamos clicar na pasta "lib" e selecionar "New Folder" (Nova Pasta) para criarmos uma nova pasta, que nomearemos como "storage" (armazenamento). Estamos separando em pastas as funcionalidades e, consequentemente, os cursos.
Dentro da pasta "storage", criaremos um novo arquivo. Para isso, clicaremos na pasta com o botão direito e selecionaremos "New File" (Novo Arquivo), que nomearemos como storage_screen.dart
. Após escrevermos o nome do arquivo e pressionarmos "Enter", o arquivo é aberto.
Na primeira linha do storage_screen.dart
, importaremos o Material, escrevendo import 'material'
e selecionando o pacote Flutter do Material entre as opções sugeridas do VS Code. Outra opção é escrever diretamente import 'package:flutter/material.dart';
.
Pressionaremos "Enter" duas vezes e codaremos o atalho stf
para encontrarmos a sugestão do "Flutter Stateful Widget". Pressionando "Enter", aparece a estrutura do Stateful Widget, que chamaremos de StorageScreen
.
import 'package:flutter/material.dart';
class StorageScreen extends StatefulWidget {
const StorageScreen({super.key});
@override
State<StorageScreen> createState() => _StorageScreenState();
}
class _StorageScreenState extends State<StorageScreen> {
@override
Widget build(BuildContext context) {
return const Placeholder()
}
}
Podemos salvar o código e acessar o main.dart
, onde adicionaremos a StorageScreen
. Dessa forma conseguiremos visualizá-la sem precisarmos passar pela HomeScreen
para chegar até ela. Quando terminarmos essa tela, faremos o caminho natural, mas é normal que, enquanto estamos criando uma tela, deixemos ela diretamente na Main para uma visualização mais rápida.
Então vamos abrir o main.dart
e descer o código até a linha do home: const RoteadorTelas(),
. Mudaremos ela para home: StorageScreen() //const RoteadorTelas()
. Como o StorageScreen()
não foi sugerido, temos uma marcação de erro nele.
Para corrigir, voltaremos para o começo do código e codaremos import 'storage/storage_screen.dart';
, importando a tela. Ao salvarmos o código, o erro some. Às vezes o analisador do Dart dá algum problema, então precisamos fazer a importação manualmente.
//código omitido
import 'storage/storage_screen.dart';
//código omitido
home: const StorageScreen(), // const RoteadorTelas(),
Abrindo o emulador, aparece uma tela preta com um "X" enorme, mostrando que não tem nada. É nesse espaço que construiremos nossa tela. Começaremos construindo a lógica da tela e depois construiremos os widgets.
Para começar, dentro da classe de estado, precisaremos de dois atributos que serão muito úteis para nós. O primeiro é a String? urlPhoto;
, ou seja, uma string que pode ser nula chamada urlPhoto
.
Esse código representa que, quando tiver uma string nessa variável, temos uma imagem a ser mostrada. Se estiver nula, não temos uma imagem. Dessa forma, construímos nossa tela pensando nessa possibilidade.
O segundo atributo que queremos é uma lista de Strings chamada de listFiles
(Lista de arquivos), lembrando que se não a iniciarmos, teremos um erro. Então codaremos List<String> listFiles; = []
. Esse atributo representa a lista de imagens que já subimos para o aplicativo. Se ela estiver vazia, não mostraremos nada.
//código omitido
class _StorageScreenState extends State<StorageScreen> {
String? urlPhoto;
List<String> listFiles = [];
@override
Widget build(BuildContext context) {
return const Placeholder()
}
}
Feitos os atributos, vamos construir os métodos. Pensaremos nos dois métodos que serão bem úteis, mas construiremos só a estrutura deles agora. O primeiro é o método de upload, que chamaremos de uploadImage()
, para subirmos uma imagem.
O outro é o método para recarregar, que chamaremos de refresh()
, para atualizarmos a tela quando fizermos alguma alteração. Na verdade, vou deixar como reload()
para seguir um padrão. Esses dois métodos são bem padrões e iremos usá-los.
//código omitido
class _StorageScreenState extends State<StorageScreen> {
String? urlPhoto;
List<String> listFiles = [];
@override
Widget build(BuildContext context) {
return const Placeholder()
}
uploadImage(){
}
reload(){
}
}
Salvamos o código e agora conseguimos criar nossa tela. No return
que está dentro do build()
, removeremos o Placeholder()
e escreveremos o Scaffold()
.
Começaremos montando a tela pela app bar, codando appBar: AppBar(title: Text("Foto de Perfil"), actions: [],)
. Colocamos o título "Foto de perfil" e deixamos o espaço para adicionarmos botões de ação.
//código omitido
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Foto de Perfil"),
actions: [],
),
}
//código omitido
Ao salvarmos o código e voltarmos para o emulador, percebemos que agora aparece uma tela com o tema do aplicativo, ou seja, o fundo verde-claro e a app bar marrom com fonte branca. No centro da app bar está escrito "Foto de Perfil".
Voltando para o código, adicionaremos dois botões em actions
. O primeiro é um IconButton()
para chamar o uploadImage()
quando pressionado, com ícone de upload.
//código omitido
actions: [
IconButton(
onPressed: () {
uploadImage();
},
icon: Icon(Icons.upload),
)
],
Ao salvarmos e abrirmos o emulador, percebemos que no canto direito da app bar apareceu um ícone de uma seta para cima com uma linha horizontal embaixo dela, que é o ícone de upload. Então ficou exatamente como queremos.
Retornando ao código, copiaremos o IconButton()
que acabamos de criar, escreveremos uma vírgula após o parêntese de fechamento do botão e, na linha abaixo, colaremos o código. Em seguida, substituiremos o ícone de upload pelo de refresh, porque não tem o de reload. Além disso, quando pressionado, ele chamará o reload()
ao invés do uploadImage()
.
Finalizadas as alterações, reparamos que os dois Icon()
no IconButton
e o Text()
no title
da AppBar()
estão com marcação de atenção. Isso porque precisamos escrever const
antes deles, já que os widgets não mudarão. Então teremos const Text("Foto de Perfil")
, const Icon(Icons.upload)
e const Icon(Icons.refresh)
.
//código omitido
appBar: AppBar(
title: const Text("Foto de Perfil"),
actions: [
IconButton(
onPressed: () {
uploadImage();
},
icon: const Icon(Icons.upload),
),
IconButton(
onPressed: () {
reload();
},
icon: const Icon(Icons.refresh),
),
],
)
Voltando para o emulador, encontramos o botão de reload, com ícone da seta circular, no canto esquerdo da app bar, ao lado do botão de upload. Percebemos que a tela está tomando forma e a aparência está quase como queremos.
Já finalizamos a app bar, agora criaremos o corpo da tela. Portanto, no código, logo após o parêntese de fechamento da app bar, escreveremos uma vírgula e, na linha abaixo, escrevermos body: Container(),
, adicionando um container ao corpo do Scaffold()
.
Esse container terá algumas características visuais, começando por uma margem de 32. Para isso, escreveremos margin: const EdgeInsets.all(32),
. Além disso, escreveremos um padding de 16, com padding: const EdgeInsets.all(16),
.
//código omitido
),
body: Container(
margin: const EdgeInsets.all(32),
padding: const EdgeInsets.all(16),
),
Salvamos o código e ao voltarmos ao emulador, reparamos que nada apareceu ainda, porque o container está vazio. Sendo assim, voltaremos ao código e usaremos o decoration
para mudar a cor e visualizarmos o conteúdo. Mudaremos a cor do container para branco e deixaremos a borda arredondada, escrevendo decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16),),
.
body: Container(
margin: const EdgeInsets.all(32),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
Salvando o código e retornando para o emulador, percebemos que apareceu um container branco com bordas arredondadas centralizado na tela, tomando a forma que queremos. Como já adicionamos os comportamentos de upload e reload, basta adicionarmos os widgets dentro desse container da forma que esperamos para termos a nossa tela do jeito que queremos.
Para fazer isso, voltaremos ao código e, após o fechamento de parêntese do BoxDecoration()
, escreveremos uma vírgula. Na linha abaixo, criaremos um filho para o container. Esse filho será uma coluna que também receberá filhos, e para isso codamos child: Column(children: []),
.
Dentro dessa coluna, o primeiro filho será um teste para descobrirmos se a urlPhoto
é ou não nula, porque declaramos que poderia ou não ser nula. Para isso, usaremos um operador ternário, escrevendo (urlPhoto != null)? :
.
Se for diferente de nulo, adicionaremos um widget, logo após o ponto de interrogação, mas veremos isso depois. Por enquanto, podemos adicionar um Container()
para testarmos. Se for nulo, ou seja, após os :
adicionaremos um CircleAvatar()
contendo um filho que será um ícone de uma pessoa.
//código omitido
child: Column(children: [
(urlPhoto != null)
? Container()
: CircleAvatar(
child: Icon(Icons.person),
),
]),
Ao salvarmos e acessarmos o emulador, percebemos que o container branco está com uma largura bem menor e está totalmente do lado esquerdo, mas na parte superior dele tem um círculo marrom com um ícone branco de pessoa dentro.
Não precisamos nos preocupar com o encolhimento do container, porque ele voltará para o tamanho correto com à medida que adicionarmos os widgets. Entretanto, o círculo do avatar está bastante pequeno, então voltaremos para o código e aumentaremos o radius
dele para 64. Para isso, escreveremos radius: 64
acima do child: Icon()
.
//código omitido
child: Column(children: [
(urlPhoto != null)
? Container()
: CircleAvatar(
radius: 64,
child: Icon(Icons.person),
),
]),
Ao salvarmos e voltarmos para a tela do emulador, percebemos que o círculo marrom aumentou para um tamanho ideal, e com isso a largura do container também aumentou um pouco. Apesar disso o ícone dentro dele continua pequeno.
Na sequência, voltaremos para o código e faremos o teste para caso haja uma imagem. Caso haja, mudaremos o código para Image.network(urlPhoto!)
, de modo que usaremos a url da própria urlPhoto
e, como já testamos que ela não é nula, colocamos um bang, que é uma exclamação, (!
) depois dela. Além disso, escreveremos um const
antes do CircleAvatar()
para tirar o aviso.
//código omitido
child: Column(children: [
(urlPhoto != null)
? Image.network(urlPhoto!)
: const CircleAvatar(
radius: 64,
child: Icon(Icons.person),
),
]),
Faremos as configurações futuras do Image.network()
quando estivermos exibindo a imagem. Depois desse teste que fizemos para mostrar a imagem, escreveremos um Divider()
abaixo do ternário.
//código omitido
child: Column(children: [
(urlPhoto != null)
? Image.network(urlPhoto!)
: const CircleAvatar(
radius: 64,
child: Icon(Icons.person),
),
Divider()
]),
Voltando para o emulador, reparamos que o Divider()
adiciona uma linha cinza bem fina após o círculo do avatar. Com essa linha, o Container voltou à largura máxima. Queremos que essa linha esteja um pouco mais distante da imagem e do conteúdo que virá após ela.
Para delimitarmos essa distância, voltaremos ao código para adicionar um padding. Para isso, clicaremos no Divider()
e depois no ícone de lâmpada, ou lupa, amarelo no lado esquerdo, selecionando "Wrap with Padding".
Aumentaremos o valor do padding para 16, ou seja, EdgeInsets.all(16.0)
. Também escreveremos um const
antes do Padding()
e apagaremos o const
antes do EdgeInsets.all()
, para o alerta que está marcando o padding
e o child
sumir.
//código omitido
child: Column(children: [
(urlPhoto != null)
? Image.network(urlPhoto!)
: const CircleAvatar(
radius: 64,
child: Icon(Icons.person),
),
const Padding(
padding: EdgeInsets.all(16.0),
child: Divider(),
),
Quando voltamos para a nossa tela no emulador, percebemos que o espaçamento aumentou, ainda que a linha seja bem fina e de difícil visualização. Para resolver isso, adicionaremos uma cor para essa linha, escrevendo color: Colors.black
dentro dos parênteses do Divider()
. Ao salvarmos o código e voltarmos para o emulador, temos uma visualização um pouco melhor da linha.
//código omitido
const Padding(
padding: EdgeInsets.all(16.0),
child: Divider(color: Colors.black),
),
Após o Padding()
, adicionaremos um texto, que será a representação do que queremos adicionar depois, que é o histórico de imagens. Codaremos const Text("Histórico de Imagens", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),),
. Dessa forma definimos o texto "Histórico de Imagens" e o estilo, que é uma fonte em negrito de tamanho 18.
//código omitido
const Padding(
padding: EdgeInsets.all(16.0),
child: Divider(color: Colors.black),
),
const Text(
"Histórico de Imagens",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
Abaixo do Text()
, criaremos outra coluna em que os filhos serão autogerados por uma lista, usando o List.generate(length, (index) => null)
. O tamanho da lista (length
) será o listFiles.length
e substituiremos a função de seta (=> null
) por uma função normal. Temos então List.generate(listFiles.length, (index){}
.
Dentro da função, passaremos as strings de URL que adicionamos a nossa lista, escrevendo String url = listFiles[index];
, e o retorno será uma Image.network()
que receberá a (url)
.
//código omitido
const Text(
"Histórico de Imagens",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
Column(
children: List.generate(
listFiles.length,
(index) {
String url = listFiles[index];
return Image.network(url);
},
),
)
Não temos nenhuma imagem para mostrar ainda, então quando voltamos para a tela do emulador, abaixo da divisão aparece apenas o título "Histórico de Imagens". Com isso, percebemos que a lógica foi estabelecida e está funcionando.
Conseguimos criar a base tela que usaremos para trabalharmos nesse curso. Já temos os botões de upload e de atualização da tela, o espaço para a imagem de perfil e outro espaço para lista com o histórico de imagens de perfil apareça também.
A seguir faremos algumas configurações necessárias para usarmos o Firebase Storage no nosso projeto.
O curso Flutter com Firebase: guardando arquivos na nuvem com Storage possui 180 minutos de vídeos, em um total de 63 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:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.