Flutter Templates
Construindo um Aplicativo de Chat em Tempo Real em Flutter com Back4App
35 min
introdução criar um aplicativo de chat envolve gerenciar dados em tempo real, autenticação de usuários, manuseio de mídia e armazenamento eficiente de dados neste tutorial, você aprenderá como construir um aplicativo de chat em tempo real no flutter que suporta conversas individuais e em grupo, status de mensagens e compartilhamento de mídia usaremos o back4app—um backend como serviço alimentado pelo parse server—para lidar com as funcionalidades de backend ao final deste tutorial, você terá um aplicativo de chat totalmente funcional com os seguintes recursos autenticação de usuário processos de registro e login seguros mensagens em tempo real entrega instantânea de mensagens usando consultas ao vivo presença do usuário rastreando o status online/offline dos usuários armazenamento de mídia enviando e recebendo imagens em chats histórico de mensagens persistindo históricos de chat para usuários pré requisitos para acompanhar este tutorial, você precisará flutter sdk instalado em sua máquina siga o guia oficial de instalação do flutter https //flutter dev/docs/get started/install conhecimento básico de flutter e dart um ide ou editor de texto como visual studio code ou android studio uma conta back4app inscreva se para uma conta gratuita em back4app https //www back4app com/ parse server sdk para flutter adicionado ao seu projeto visão geral vamos construir um aplicativo de chat com os seguintes componentes autenticação de usuário os usuários podem se inscrever e fazer login lista de contatos exibir uma lista de usuários para conversar tela de chat interface de mensagens em tempo real compartilhamento de mídia capacidade de enviar e receber imagens status online exibir status online/offline dos usuários passo 1 – configurando o projeto flutter 1 1 criar um novo projeto flutter abra seu terminal e execute flutter create chat app navegue até o diretório do projeto cd chat app 1 2 adicionar dependências abra pubspec yaml e adicione as seguintes dependências dependencies flutter sdk flutter parse server sdk flutter ^4 0 1 image picker ^0 8 4+3 cached network image ^3 2 0 uuid ^3 0 4 execute flutter pub get para instalar os pacotes parse server sdk flutter interagir com o back4app image picker selecionar imagens da galeria ou câmera cached network image carregamento e cache eficientes de imagens uuid gerar identificadores únicos passo 2 – configurando o back4app 2 1 criar um novo aplicativo back4app faça login no seu painel do back4app https //dashboard back4app com/ clique em "criar novo app" digite um nome para seu aplicativo, por exemplo, "chatapp" , e clique em "criar" 2 2 configurar classes e modelo de dados vamos criar as seguintes classes usuário (classe parse padrão) armazena informações do usuário mensagem armazena mensagens de chat saladechat representa um chat entre usuários 2 2 1 classe usuário campos nome de usuário string senha string email string estáonline boolean avatar arquivo (opcional) a classe usuário é embutida; só precisamos garantir que tenha os campos adicionais 2 2 2 classe mensagem campos remetente pointer< user> destinatário pointer< user> chatroomid string conteúdo string imagem arquivo (opcional) criadoem datetime (adicionado automaticamente) 2 2 3 classe chatroom campos chatroomid string usuários array of pointer< user> últimamensagem string atualizadoem datetime (atualizado automaticamente) 2 3 obter credenciais da aplicação navegue até configurações do app > segurança & chaves anote seu id da aplicação e chave do cliente passo 3 – inicializando o parse em seu app flutter abra lib/main dart e modifique o da seguinte forma import 'package\ flutter/material dart'; import 'package\ parse server sdk flutter/parse server sdk dart'; import 'screens/login screen dart'; // you'll create this file next void main() async { widgetsflutterbinding ensureinitialized(); const keyapplicationid = 'your application id'; const keyclientkey = 'your client key'; const keyparseserverurl = 'https //parseapi back4app com'; await parse() initialize( keyapplicationid, keyparseserverurl, clientkey keyclientkey, autosendsessionid true, debug true, ); runapp(myapp()); } class myapp extends statelesswidget { @override widget build(buildcontext context) { return materialapp( title 'chat app', theme themedata( primaryswatch colors blue, ), home loginscreen(), ); } } substitua 'your application id' e 'your client key' pelas suas credenciais reais do back4app passo 4 – implementando a autenticação do usuário 4 1 criar serviço de autenticação crie um novo diretório chamado services sob lib e adicione um arquivo chamado auth service dart // lib/services/auth service dart import 'package\ parse server sdk flutter/parse server sdk dart'; class authservice { future\<parseuser?> signup(string username, string email, string password) async { var user = parseuser(username, password, email); var response = await user signup(); if (response success) { return user; } else { return null; } } future\<parseuser?> login(string username, string password) async { var user = parseuser(username, password, null); var response = await user login(); if (response success) { return user; } else { return null; } } future\<void> logout() async { var user = await parseuser currentuser() as parseuser?; await user? logout(); } future\<parseuser?> getcurrentuser() async { return await parseuser currentuser() as parseuser?; } } 4 2 criar telas de login e cadastro crie um novo diretório chamado screens sob lib e adicione arquivos nomeados login screen dart e signup screen dart 4 2 1 tela de login // lib/screens/login screen dart import 'package\ flutter/material dart'; import ' /services/auth service dart'; import 'home screen dart'; import 'signup screen dart'; class loginscreen extends statelesswidget { final authservice authservice = authservice(); final texteditingcontroller usernamecontroller = texteditingcontroller(); final texteditingcontroller passwordcontroller = texteditingcontroller(); void login(buildcontext context) async { var user = await authservice login( usernamecontroller text trim(), passwordcontroller text trim(), ); if (user != null) { navigator pushreplacement( context, materialpageroute(builder (context) => homescreen()), ); } else { scaffoldmessenger of(context) showsnackbar(snackbar(content text('login failed'))); } } @override widget build(buildcontext context) { return scaffold( appbar appbar( title text('chat app login'), ), body padding( padding const edgeinsets all(16), child column( children \[ textfield( controller usernamecontroller, decoration inputdecoration(labeltext 'username'), ), textfield( controller passwordcontroller, decoration inputdecoration(labeltext 'password'), obscuretext true, ), sizedbox(height 20), elevatedbutton( onpressed () => login(context), child text('login'), ), textbutton( onpressed () => navigator push( context, materialpageroute(builder (context) => signupscreen())), child text('don\\'t have an account? sign up'), ), ], ), ), ); } } 4 2 2 tela de inscrição // lib/screens/signup screen dart import 'package\ flutter/material dart'; import ' /services/auth service dart'; import 'home screen dart'; class signupscreen extends statelesswidget { final authservice authservice = authservice(); final texteditingcontroller usernamecontroller = texteditingcontroller(); final texteditingcontroller emailcontroller = texteditingcontroller(); final texteditingcontroller passwordcontroller = texteditingcontroller(); void signup(buildcontext context) async { var user = await authservice signup( usernamecontroller text trim(), emailcontroller text trim(), passwordcontroller text trim(), ); if (user != null) { navigator pushreplacement( context, materialpageroute(builder (context) => homescreen()), ); } else { scaffoldmessenger of(context) showsnackbar(snackbar(content text('sign up failed'))); } } @override widget build(buildcontext context) { return scaffold( appbar appbar( title text('chat app sign up'), ), body padding( padding const edgeinsets all(16), child column( children \[ textfield( controller usernamecontroller, decoration inputdecoration(labeltext 'username'), ), textfield( controller emailcontroller, decoration inputdecoration(labeltext 'email'), ), textfield( controller passwordcontroller, decoration inputdecoration(labeltext 'password'), obscuretext true, ), sizedbox(height 20), elevatedbutton( onpressed () => signup(context), child text('sign up'), ), ], ), ), ); } } passo 5 – implementando a presença do usuário 5 1 atualizar o status online do usuário crie um método em authservice para atualizar o status online do usuário // lib/services/auth service dart class authservice { // existing methods future\<void> updateuseronlinestatus(bool isonline) async { var user = await parseuser currentuser() as parseuser?; if (user != null) { user set('isonline', isonline); await user save(); } } } 5 2 definir o status online no login e logout atualize os métodos de login e logout // lib/services/auth service dart class authservice { // existing methods future\<parseuser?> login(string username, string password) async { var user = parseuser(username, password, null); var response = await user login(); if (response success) { await updateuseronlinestatus(true); return user; } else { return null; } } future\<void> logout() async { await updateuseronlinestatus(false); var user = await parseuser currentuser() as parseuser?; await user? logout(); } } passo 6 – exibindo lista de contatos 6 1 criar serviço de usuário criar user service dart em services // lib/services/user service dart import 'package\ parse server sdk flutter/parse server sdk dart'; class userservice { future\<list\<parseuser>> getallusers() async { final querybuilder\<parseuser> queryusers = querybuilder\<parseuser>(parseuser forquery()); final parseresponse apiresponse = await queryusers query(); if (apiresponse success && apiresponse results != null) { return apiresponse results as list\<parseuser>; } else { return \[]; } } } 6 2 criar tela inicial // lib/screens/home screen dart import 'package\ flutter/material dart'; import 'package\ parse server sdk flutter/parse server sdk dart'; import ' /services/user service dart'; import ' /services/auth service dart'; import 'chat screen dart'; class homescreen extends statefulwidget { @override homescreenstate createstate() => homescreenstate(); } class homescreenstate extends state\<homescreen> { final userservice userservice = userservice(); final authservice authservice = authservice(); list\<parseuser> users = \[]; parseuser? currentuser; @override void initstate() { super initstate(); fetchusers(); getcurrentuser(); } void getcurrentuser() async { currentuser = await authservice getcurrentuser(); } void fetchusers() async { var allusers = await userservice getallusers(); setstate(() { users = allusers where((user) => user username != currentuser? username) tolist(); }); } void logout(buildcontext context) async { await authservice logout(); navigator pushreplacementnamed(context, '/'); } @override widget build(buildcontext context) { return scaffold( appbar appbar( title text('contacts'), actions \[ iconbutton(onpressed () => logout(context), icon icon(icons logout)), ], ), body listview\ builder( itemcount users length, itembuilder (context, index) { var user = users\[index]; return listtile( title text(user username ?? ''), subtitle text(user get('isonline') == true ? 'online' 'offline'), ontap () { navigator push( context, materialpageroute(builder (context) => chatscreen(receiver user)), ); }, ); }, ), ); } } passo 7 – implementando mensagens em tempo real com consultas ao vivo 7 1 configurar cliente de consulta ao vivo adicione a seguinte dependência em pubspec yaml dependencies flutter sdk flutter parse server sdk flutter git url https //github com/parse community/parse sdk flutter git ref master isso garante que você tenha a versão mais recente com suporte a live query 7 2 inicializar live query em main dart // lib/main dart void main() async { widgetsflutterbinding ensureinitialized(); const keyapplicationid = 'your application id'; const keyclientkey = 'your client key'; const keyparseserverurl = 'https //parseapi back4app com'; const livequeryurl = 'wss\ //your app back4app io'; await parse() initialize( keyapplicationid, keyparseserverurl, clientkey keyclientkey, autosendsessionid true, debug true, livequeryurl livequeryurl, ); runapp(myapp()); } substitua 'your app' pelo seu subdomínio da aplicação back4app 7 3 criar tela de chat // lib/screens/chat screen dart import 'package\ flutter/material dart'; import 'package\ parse server sdk flutter/parse server sdk dart'; import 'package\ uuid/uuid dart'; import 'package\ image picker/image picker dart'; import ' /services/auth service dart'; import 'package\ cached network image/cached network image dart'; class chatscreen extends statefulwidget { final parseuser receiver; chatscreen({required this receiver}); @override chatscreenstate createstate() => chatscreenstate(); } class chatscreenstate extends state\<chatscreen> { final authservice authservice = authservice(); parseuser? currentuser; list\<parseobject> messages = \[]; string chatroomid = ''; final texteditingcontroller messagecontroller = texteditingcontroller(); late parselivelist\<parseobject> livemessages; @override void initstate() { super initstate(); getcurrentuser(); setupchatroom(); } void getcurrentuser() async { currentuser = await authservice getcurrentuser(); if (currentuser != null) { setuplivequery(); } } void setupchatroom() { string userid = currentuser! objectid!; string receiverid = widget receiver objectid!; chatroomid = userid compareto(receiverid) > 0 ? '$userid $receiverid' '$receiverid $userid'; } void setuplivequery() { querybuilder\<parseobject> querymessages = querybuilder\<parseobject>(parseobject('message')) whereequalto('chatroomid', chatroomid) orderbyascending('createdat'); livemessages = parselivelist\<parseobject>( querymessages, listeningincludes \['sender'], lazyloading false, ); setstate(() {}); } void sendmessage({string? content, parsefilebase? imagefile}) async { var message = parseobject('message') set('sender', currentuser) set('receiver', widget receiver) set('chatroomid', chatroomid) set('content', content ?? '') set('image', imagefile); await message save(); messagecontroller clear(); } void pickimage() async { final picker = imagepicker(); final pickedfile = await picker pickimage(source imagesource gallery, imagequality 50); if (pickedfile != null) { var file = parsefile(file(pickedfile path)); await file save(); sendmessage(imagefile file); } } @override void dispose() { livemessages dispose(); super dispose(); } @override widget build(buildcontext context) { if (currentuser == null || livemessages == null) { return scaffold( appbar appbar( title text('chat with ${widget receiver username}'), ), body center(child circularprogressindicator()), ); } return scaffold( appbar appbar( title text('chat with ${widget receiver username}'), ), body column( children \[ expanded( child parselivelistwidget\<parseobject>( query livemessages, reverse false, lazyloading false, childbuilder (context, snapshot) { if (snapshot failed) { return text('error ${snapshot error}'); } else if (snapshot hasdata) { var message = snapshot loadeddata!; var sender = message get\<parseuser>('sender')!; var isme = sender objectid == currentuser! objectid; var content = message get\<string>('content') ?? ''; var image = message get\<parsefilebase>('image'); return align( alignment isme ? alignment centerright alignment centerleft, child container( padding edgeinsets all(8), margin edgeinsets all(8), decoration boxdecoration( color isme ? colors blue\[100] colors grey\[300], borderradius borderradius circular(8), ), child column( crossaxisalignment crossaxisalignment start, children \[ if (content isnotempty) text(content), if (image != null) cachednetworkimage( imageurl image url!, placeholder (context, url) => circularprogressindicator(), errorwidget (context, url, error) => icon(icons error), ), ], ), ), ); } else { return container(); } }, ), ), divider(height 1), container( padding edgeinsets symmetric(horizontal 8), color colors white, child row( children \[ iconbutton( icon icon(icons photo), onpressed pickimage, ), expanded( child textfield( controller messagecontroller, decoration inputdecoration collapsed(hinttext 'type a message'), ), ), iconbutton( icon icon(icons send), onpressed () { if ( messagecontroller text trim() isnotempty) { sendmessage(content messagecontroller text trim()); } }, ), ], ), ), ], ), ); } } explicação parselivelistwidget um widget que escuta atualizações de consultas ao vivo e se reconstrói quando os dados mudam sendmessage() envia uma mensagem de texto ou uma imagem pickimage() usa image picker para selecionar uma imagem e a envia como uma mensagem setuplivequery() configura uma consulta ao vivo para escutar novas mensagens na sala de chat passo 8 – testando o app 8 1 execute o app no seu terminal, execute flutter run 8 2 testar mensagens abra o aplicativo em dois dispositivos ou emuladores cadastre se ou faça login com contas de usuário diferentes de uma conta, selecione o outro usuário na lista de contatos envie mensagens e imagens; elas devem aparecer em tempo real em ambos os dispositivos conclusão parabéns! você construiu um aplicativo de chat em tempo real no flutter usando o back4app este aplicativo suporta autenticação de usuário login e cadastro seguros mensagens em tempo real atualizações instantâneas usando consultas ao vivo presença do usuário rastreamento de status online/offline compartilhamento de mídia envio e recebimento de imagens histórico de mensagens persistência de mensagens de chat próximos passos chats em grupo estender o aplicativo para suportar conversas em grupo status das mensagens implementar recibos de leitura e indicadores de digitação notificações push enviar notificações para novas mensagens quando o aplicativo estiver em segundo plano fotos de perfil permitir que os usuários enviem avatares melhorias de segurança proteger dados com acls e permissões baseadas em função recursos adicionais documentação do back4app https //www back4app com/docs guia do parse sdk para flutter https //docs parseplatform org/flutter/guide/ documentação oficial do flutter https //flutter dev/docs consulta ao vivo do parse https //docs parseplatform org/js/guide/#live queries feliz codificação!