Criando um Chat Completo Usando Flutter e Firebase – Parte 1
Está cada vez mais comum acompanharmos o surgimento de novos aplicativos de interação social sendo criados por aí, e por isso, decidi criar um chat usando Flutter e Firebase.
Pensei que seria bem legal e desafiador escrever uma aplicação de conversa em tempo real e depois transformar em artigo para que mais pessoas pudessem acompanhar o desenvolvimento de algo desse tipo.
Esse artigo será dividido em 3 partes: chat, envio de mídia e confirmação de leitura.
Primeiro, vamos escolher o nome do nosso aplicativo. O nome do meu será “MyChat”, por ser um nome curto e objetivo.
Vamos criar o projeto Flutter, para isso digite o comando:
flutter create mychat
Depois que o nosso projeto for gerado, vamos acessar o diretório para podermos instalar algumas dependências do Firebase.
cd mychat
Adicionando as dependências e configurando o Firebase
Agora iremos adicionar nas dependências do projeto os pacotes firebase_core e cloud_firestore, pacotes importantes para podermos configurar e nos comunicar com o Firebase.
flutter pub add firebase_core
flutter pub add cloud_firestore
Também vamos adicionar a dependência que vai nos ajudar no momento de trabalhar com datas:
flutter pub add intl
Agora, precisamos instalar o Firebase CLI para preparar o nosso app com as configurações do nosso projeto no Firebase.
dart pub global activate flutterfire_cli
Antes de seguirmos, precisamos fazer a autenticação no Firebase CLI que acabamos de instalar, digite o comando:
firebase login
Agora que estamos autenticados no Firebase CLI, precisamos gerar o arquivo de configuração do Firebase para que a integração seja feita de fato. Para isto, precisamos digitar o seguinte comando:
flutterfire configure
Atenção: Se você estiver recebendo uma mensagem de erro com a mensagem “uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:cloud_firestore]”, vá ao arquivo android/app/build.gradle e mude a versão do minSdkVersion de 16 para 21.
Note que será apresentado alguns passos para preenchermos com informações que ficarão registradas no nosso arquivo de configuração firebase_options.dart que será gerado no final.
Preencheremos da seguinte forma:
Select a Firebase project to configure your Flutter application with: Escolha a opção <create a new project>
Enter a project id for your new Firebase project: Dê um nome único ao seu projeto no Firebase, o meu ficará my-chat-[id_aleatório].
Which platforms should your configuration support: Nosso tutorial será feito no Android, então só selecionarei Android.
No final será perguntado se você deseja atualizar os arquivos build.gradle, é só pressionar Enter para aceitar e finalizar.
Um arquivo firebase_options.dart foi criado na raiz da estrutura do projeto, nele fica reunido todas as informações necessárias para se conectar com o nosso projeto no FIrebase.
Agora precisamos ir no arquivo lib/main.dart e inserir o código que inicializa o Firebase na aplicação. Acrescente o seguinte bloco de código no método main(). Não esqueça de colocar o async no método main() e de importar o pacote firebase_core e o arquivo firebase_options que se encontra na pasta /lib.
Acabamos de colocar o método que inicializa o Firebase na nossa aplicação, agora precisamos criar a model da mensagem.
Model
No diretório /lib, crie uma pasta chamada models e dentro dela crie um arquivo chamado message_model.dart. A classe da nossa model terá três propriedades, são elas: author, message e timestamp. O código ficará assim:
Provider
Depois da nossa model criada, vamos escrever o código do nosso provider. O provider ficará responsável pelas requisições feitas no Firestore, é nele que criaremos os métodos que envia as mensagens e que recupera as mensagens enviadas.
No diretório /lib, crie uma pasta chamada providers e dentro dela crie um arquivo chamado chat_provider.dart.
O código ficará da seguinte forma:
Tela da “sala de entrada”
No diretório lib, crie uma pasta chamada “screens” e nela crie um arquivo “enter_room_screen.dart”, ficando da seguinte forma: lib/screens/enter_room_screen.dart. Nessa tela o usuário poderá inserir um nickname no input que criamos, logo abaixo terá um botão para ele entrar na sala do chat.
O código ficará da seguinte forma:
No código acima montamos uma tela que terá um título, um campo de texto para o usuário inserir o nickname e um campo para entrar na sala.
Agora vou explicar o que cada parte do código faz:
_nicknameEditingController: Controller que salvará o valor do campo nickname
_nicknameInput: Cria um widget com um campo de texto
_enterButton: Botão para entrar no chat, onde seu evento principal é redirecionar o usuário para a rota “/chat” e enviar o valor do nickname como argumento. Logo após o usuário ser redirecionado, o campo nickname é limpo usando o método clear() do nicknameEditingController.
Você deve estar se perguntando, “mas que rota é essa?“…bom, mais pra frente eu explico.
Tela da conversa
Criaremos agora a tela do chat, onde o usuário poderá visualizar todas as mensagens, digitar mensagens e enviar.
No diretório lib/screens, crie um arquivo chamado chat_screen.dart.
Na nossa classe iremos criar uma instância do Firestore:
Depois precisamos criar um controlador de input para armazenar a mensagem que o usuário pretende enviar:
Agora, temos que criar a variável onde será armazenada as mensagens, assim que tivermos um retorno da nossa requisição no Firebase:
Até o momento nossa classe se parecerá com isso:
Agora precisamos criar dois métodos, um que será responsável pelo envio das mensagens e outro para pegarmos o nickname do usuário.
sendMessage: Responsável por registrar as mensagens no Firestore,
onde receberá o texto da mensagem como seu único argumento. Além de criar novos registros no Firebase, a função fica responsável por limpar nosso input de mensagem e também de descer o scroll para a base de uma forma suavizada.
Perceba que antes de enviar a mensagem, nós verificamos se o campo da mensagem está vazio, se estiver, o método de enviar não é chamado. Também, usamos a função trim() ao enviar a mensagem, para que espaços em brancos antes e depois da mensagem sejam removidos.
currentUser: Nos retorna o nickname que foi passado como argumento para a rota.
Nosso método sendMessage ficará assim:
E o nosso método de pegar o nickname do usuário ficará desse jeito:
Depois dessas adições, nosso código ficará assim:
Nada será renderizado na nossa tela, ainda. Precisamos criar nossos widgets.
Veja na imagem abaixo quais widgets iremos criar:
Começaremos criando nosso widget do timestamp, como o nome já diz será um widget para exibir a data e hora que a mensagem foi enviada. No diretório lib, crie um diretório chamado widgets, dentro deste diretório crie um arquivo com o nome message_timestamp.dart. Nosso código ficará assim:
Perceba que nosso widget recebe um argumento com o timestamp que irá ser exibido logo após formatarmos usando o DateFormat do Intl. Na linha anterior a esta, criamos uma constante chamada datePattern para determinarmos como queremos que o timestamp seja formatado.
Agora, criaremos nosso widget do message bubble. No diretório lib/widgets, crie um arquivo chamado message_bubble.dart.
Nosso widget recebe dois argumentos: chatMessage e isMe.
chatMessage: um objeto da mensagem com as propriedades author, timestamp e message.
isMe: um boolean para determinar se a mensagem está sendo enviada ou recebida. Isto vai nos ajudar a diferenciar o próprio widget, estilizando e posicionando corretamente.
O código ficará assim:
Precisamos criar agora o input onde o usuário digitará a mensagem a ser enviada, no diretório lib/widgets, iremos criar o arquivo chamado input_message.dart.
Note que estaremos recebendo por argumento o controller do input e o método responsável pelo envio da mensagem.
Com os nossos widgets já escritos, vamos voltar no arquivo chat_screen.dart, terminar de montar a tela e importar os novos widgets.
Para que possamos atualizar as mensagens em tempo real, vamos usar o StreamBuilder, explicarei um pouco mais sobre…
StreamBuilder é um widget que converte um fluxo de objetos em widgets. De forma simples, ele escuta o stream e se constrói em cada novo evento emitido. Os eventos emitidos determinam se o fluxo de dados conseguiu ou não se conectar, se está em espera, se terminou ou se iniciou mas não terminou.
Nosso body vai ficar da seguinte forma:
Estamos usando o widget Stack para que possamos empilhar nosso input e o restante das mensagens. Também, fazemos verificações para saber se as mensagens estão carregando e se tem ou não mensagens para que assim o estado da tela mude. Se houver mensagens, obviamente vai mostrá-las, se ainda estiver carregando então mostre um loading e se não possuir mensagem alguma, mostre um aviso que está sem mensagem.
O resultado do chat_screen.dart, será:
Para finalizarmos, precisamos criar as rotas das nossas páginas no arquivo main.dart.
No widget MaterialApp, vamos informar nossas duas rotas no argumento routes e também vamos informar nossa rota inicial em initialRoute, que será “/”.
Nossa classe MyApp ficará assim:
O resultado do arquivo main.dart, será:
Rode o projeto e veja o resultado, nosso app parecerá com isto:
Concluindo a criação de um chat usando Flutter e Firebase
O Firebase é uma ótima ferramenta quando precisamos fazer algo rápido e fácil, sem que para isso seja necessário criar todo um projeto backend que levaria algumas horas ou quem sabe dias.
Quando unimos o Flutter e Firebase, conseguimos construir boas ferramentas de forma eficiente e gratuita (até um certo ponto). À medida que sua aplicação cresce, o Firebase começa a cobrar pelas requisições, então recomendo dar uma olhadinha nos preços caso queira começar um projeto grande com ele.
Se você não sabe o que é um widget e como funciona, recomendo dar uma olhadinha neste outro artigo aqui no blog da Taller.