Artigo original: How to Build a Chat App UI With Flutter and Dart
Atualmente, muitas pessoas usam aplicações de bate-papo para se comunicar com membros da equipe, amigos e familiares por meio de seus smartphones. Isso torna essas aplicações um meio essencial de comunicação.
Há também uma alta demanda por interfaces de usuário intuitivas e poderosas com recursos de última geração. A interface do usuário (ou UI) é o aspecto mais impactante da experiência geral do usuário. Por isso, é importante acertar.
O desenvolvimento de aplicações com o Flutter conquistou o mundo em termos de desenvolvimento de aplicações para dispositivos móveis multiplataforma. Você pode usá-lo para criar interfaces de usuário perfeitas. Muitas empresas de desenvolvimento usam o Flutter hoje.
Neste tutorial, apresentarei a você uma mistura de ambos: criaremos uma interface de usuário de aplicação de bate-papo inteiramente no ambiente de codificação do Flutter/Dart. Além de aprender a incrível implementação da interface do usuário de bate-papo no Flutter, também aprenderemos como funcionam seus fluxos de trabalho e estruturas de codificação.
Então, vamos começar!
Como criar um projeto no Flutter
Primeiro, precisamos criar um projeto no Flutter. Para isso, certifique-se de ter instalado o Flutter SDK e outros requisitos relacionados ao desenvolvimento de aplicações do Flutter.
Se tudo estiver configurado corretamente, para criar um projeto, podemos simplesmente executar o seguinte comando em nosso diretório local desejado:
flutter create ChatApp
Depois de configurar o projeto, podemos navegar dentro do diretório do projeto e executar o seguinte comando no terminal para executar o projeto em um emulador disponível ou em um dispositivo real:
flutter run
Depois de construído com sucesso, obteremos o seguinte resultado na tela do emulador:

Como criar a UI da tela inicial principal
Agora, vamos começar a criar a interface de usuário (UI) para nossa aplicação de bate-papo. A tela inicial principal conterá 2 seções:
- a tela de conversa, que vamos implementar como uma página separada; e
- uma barra de navegação inferior.
Primeiro, precisamos fazer algumas configurações simples no código boilerplate padrão, no arquivo main.dart. Vamos remover um pouco do código padrão e adicionar MaterialApp
, apontando para o Container
vazio como uma página inicial por enquanto:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: Container(),
);
}
}
Agora, no lugar do widget Container
vazio, vamos chamar o widget de tela HomePage
. Primeiro, no entanto, precisamos implementar a tela.
Como criar a tela inicial principal
Dentro do diretório ./lib da nossa pasta raiz do projeto, precisamos criar uma pasta chamada ./screens. Essa pasta conterá todos os arquivos dart para diferentes telas.
Dentro do diretório ./lib/screens/, precisamos criar um arquivo chamado homePage.dart. Dentro do arquivo homePage.dart, precisamos adicionar o código básico do StatelessWidget, conforme mostrado no trecho de código abaixo:
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(child: Text("Chat")),
),
);
}
}
Agora, precisamos chamar o widget de classe HomePage
no arquivo main.dart conforme mostrado no trecho de código abaixo:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
Depois, vamos obter o resultado conforme mostrado na captura de tela do emulador abaixo:

Como criar a barra de navegação inferior
Vamos colocar um menu de navegação inferior na tela HomePage
. Para isso, vamos utilizar o widget BottomNavigationBar
no parâmetro bottomNavigationBar
, fornecido pelo widget Scaffold
.
Aqui está a implementação geral do código:
return Scaffold(
body: Container(
child: Center(child: Text("Chat")),
),
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.red,
unselectedItemColor: Colors.grey.shade600,
selectedLabelStyle: TextStyle(fontWeight: FontWeight.w600),
unselectedLabelStyle: TextStyle(fontWeight: FontWeight.w600),
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.message),
title: Text("Chats"),
),
BottomNavigationBarItem(
icon: Icon(Icons.group_work),
title: Text("Channels"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box),
title: Text("Profile"),
),
],
),
);
Aqui, configuramos um BottomNavigationBar
com vários parâmetros de estilo e mantivemos nosso item de menu de navegação no parâmetro items
. Para o parâmetro body
, usamos um simples Container
com um widget Text
.
Então, obteremos a barra de navegação inferior, conforme mostrado na captura de tela do emulador abaixo:

Com a navegação inferior concluída, podemos prosseguir e implementar a seção da lista de conversas, logo acima da barra de navegação inferior.
Como criar a tela de lista de conversas
Aqui, criaremos a seção da lista de conversas, que conterá uma seção de cabeçalho, uma barra de pesquisa e a visualização da lista de conversas.
Primeiro, dentro da pasta ./lib/screens, precisamos criar um arquivo dart chamado chatPage.dart. Em seguida, adicionamos um modelo de classe de widget com estado simples dentro dele, conforme mostrado no trecho de código abaixo:
import 'package:flutter/material.dart';
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Center(child: Text("Chat")),
),
);
}
}
Depois, precisamos chamar o widget de classe chatPage
no lugar do widget Container
em homePage.dart, conforme mostrado no trecho de código abaixo:
return Scaffold(
body: ChatPage(),
bottomNavigationBar: BottomNavigationBar(
Isso nos dará o seguinte resultado, conforme mostrado no emulador abaixo:

Como criar um cabeçalho de página de lista de conversas
Agora, vamos adicionar o cabeçalho à seção da lista de conversas, que terá um cabeçalho de texto e um botão. O código completo de implementação da interface do usuário é fornecido no trecho de código abaixo:
return Scaffold(
body: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SafeArea(
child: Padding(
padding: EdgeInsets.only(left: 16,right: 16,top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("Conversations",style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold),),
Container(
padding: EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2),
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.pink[50],
),
child: Row(
children: <Widget>[
Icon(Icons.add,color: Colors.pink,size: 20,),
SizedBox(width: 2,),
Text("Add New",style: TextStyle(fontSize: 14,fontWeight: FontWeight.bold),),
],
),
)
],
),
),
),
],
),
),
);
Aqui, usamos o SingleChildScrollView
para que a seção do corpo do chatPage.dart seja totalmente rolável.
Em seguida, usamos a instância BouncingScrollPhysics
para fornecer o efeito de salto quando a rolagem de um usuário atinge o final ou o início.
Depois disso, adicionamos um widget Text
e um Container
para exibir a parte inferior do lado direito.
Por fim, temos um widget Column
como filho do widget SingleChildScrollView
para que tudo apareça verticalmente na tela.
Isso nos dará o seguinte resultado conforme mostrado no emulador abaixo:

Logo depois, vamos adicionar uma barra de pesquisa logo abaixo da seção do cabeçalho.
Como adicionar uma barra de pesquisa
No widget Column
de antes, vamos adicionar um widget de barra de pesquisa, logo abaixo da UI da seção do cabeçalho. Assim, como segundo filho do widget Column
, precisamos inserir o seguinte código fornecido no trecho de código abaixo:
Padding(
padding: EdgeInsets.only(top: 16,left: 16,right: 16),
child: TextField(
decoration: InputDecoration(
hintText: "Search...",
hintStyle: TextStyle(color: Colors.grey.shade600),
prefixIcon: Icon(Icons.search,color: Colors.grey.shade600, size: 20,),
filled: true,
fillColor: Colors.grey.shade100,
contentPadding: EdgeInsets.all(8),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(
color: Colors.grey.shade100
)
),
),
),
),
Isso nos dará o seguinte resultado conforme mostrado no emulador abaixo:

Como criar a lista de conversas
Tendo a seção do cabeçalho e uma barra de pesquisa, vamos implementar a seção da lista de conversas.
Para isso, precisamos implementar um modelo de objeto de classe para armazenar as instâncias das listas de conversas.
Então, dentro da pasta ./lib, precisamos criar uma pasta chamada ./models. Dentro de ./models, precisamos criar um arquivo chamado chatUsersModel.dart.
No arquivo de modelo, precisamos criar uma classe de objeto de modelo, conforme mostrado no trecho de código abaixo:
import 'package:flutter/cupertino.dart';
class ChatUsers{
String name;
String messageText;
String imageURL;
String time;
ChatUsers({@required this.name,@required this.messageText,@required this.imageURL,@required this.time});
}
O objeto abrigará o nome do usuário, a mensagem de texto, o URL da imagem e a hora.
Em seguida, precisamos criar uma lista de usuários dentro do chatPage.dart conforme mostrado no trecho de código abaixo:
class _ChatPageState extends State<ChatPage> {
List<ChatUsers> chatUsers = [
ChatUsers(text: "Jane Russel", secondaryText: "Awesome Setup", image: "images/userImage1.jpeg", time: "Now"),
ChatUsers(text: "Glady's Murphy", secondaryText: "That's Great", image: "images/userImage2.jpeg", time: "Yesterday"),
ChatUsers(text: "Jorge Henry", secondaryText: "Hey where are you?", image: "images/userImage3.jpeg", time: "31 Mar"),
ChatUsers(text: "Philip Fox", secondaryText: "Busy! Call me in 20 mins", image: "images/userImage4.jpeg", time: "28 Mar"),
ChatUsers(text: "Debra Hawkins", secondaryText: "Thankyou, It's awesome", image: "images/userImage5.jpeg", time: "23 Mar"),
ChatUsers(text: "Jacob Pena", secondaryText: "will update you in evening", image: "images/userImage6.jpeg", time: "17 Mar"),
ChatUsers(text: "Andrey Jones", secondaryText: "Can you please share the file?", image: "images/userImage7.jpeg", time: "24 Feb"),
ChatUsers(text: "John Wick", secondaryText: "How are you?", image: "images/userImage8.jpeg", time: "18 Feb"),
];
Agora que temos os dados da lista de conversas dos usuários fictícios, podemos aplicá-los à lista de conversas para criar uma exibição de lista.
Como criar um widget de classe separado para conversa individual
Aqui, vamos criar um widget de componente separado para os itens individuais na exibição da lista de conversas.
Para isso, dentro de ./lib, crie uma pasta chamada ./widgets. Dentro de ./widgets, precisamos criar um arquivo chamado conversationList.dart. Dentro do novo arquivo de widget, podemos usar o código do seguinte trecho de código:
import 'package:flutter/material.dart';
class ConversationList extends StatefulWidget{
String name;
String messageText;
String imageUrl;
String time;
bool isMessageRead;
ConversationList({@required this.name,@required this.messageText,@required this.imageUrl,@required this.time,@required this.isMessageRead});
@override
_ConversationListState createState() => _ConversationListState();
}
class _ConversationListState extends State<ConversationList> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
},
child: Container(
padding: EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(widget.imageUrl),
maxRadius: 30,
),
SizedBox(width: 16,),
Expanded(
child: Container(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget.name, style: TextStyle(fontSize: 16),),
SizedBox(height: 6,),
Text(widget.messageText,style: TextStyle(fontSize: 13,color: Colors.grey.shade600, fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),),
],
),
),
),
],
),
),
Text(widget.time,style: TextStyle(fontSize: 12,fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),),
],
),
),
);
}
}
Esse arquivo de widget usa o nome do usuário, a mensagem de texto, o URL da imagem, a hora e um valor de tipo de mensagem booleana como parâmetros. Ele retorna o template contendo os valores.
No chatPage.dart, dentro do widget ListView
, precisamos chamar o widget ConversationList
passando os parâmetros necessários conforme mostrado no trecho de código abaixo:
ListView.builder(
itemCount: chatUsers.length,
shrinkWrap: true,
padding: EdgeInsets.only(top: 16),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index){
return ConversationList(
name: chatUsers[index].name,
messageText: chatUsers[index].messageText,
imageUrl: chatUsers[index].imageURL,
time: chatUsers[index].time,
isMessageRead: (index == 0 || index == 3)?true:false,
);
},
),
Observe que este widget ListView
deve ser mantido como o primeiro filho do widget Column
na tela chatPage
.
Isso nos dará o seguinte resultado conforme mostrado no emulador abaixo:

Isso conclui a implementação da interface do usuário de nossa tela de lista de conversas e tela principal da página inicial como um todo. Agora, passaremos para a implementação da tela de detalhes do bate-papo.
Como criar uma tela de detalhes do bate-papo
Agora, vamos criar uma tela de detalhes do bate-papo. Para isso, precisamos criar um arquivo chamado chatDetailPage.dart dentro da pasta ./lib/screens/. Por enquanto, vamos apenas adicionar o código básico conforme mostrado no trecho de código abaixo:
import 'package:flutter/material.dart';
class ChatDetailPage extends StatefulWidget{
@override
_ChatDetailPageState createState() => _ChatDetailPageState();
}
class _ChatDetailPageState extends State<ChatDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Chat Detail"),
),
body: Container()
);
}
}
Aqui, retornamos um AppBar
básico com Text
e um Container
vazio como o body
do widget Scaffold
.
Agora, vamos adicionar navegação para ChatDetailPage
no método onTap
do widget GestureHandler
no arquivo do widget conversationList.dart, conforme mostrado no trecho de código abaixo:
GestureDetector(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context){
return ChatDetailPage();
}));
},
Podemos navegar para a tela de detalhes do bate-papo, conforme mostrado na demonstração do emulador abaixo:

Como criar uma barra de aplicações personalizada para tela de detalhes do bate-papo
Aqui, vamos adicionar uma barra de aplicações personalizada na parte superior da tela de detalhes do bate-papo. Para isso, vamos utilizar o widget AppBar
com diversas configurações de parâmetros conforme o trecho de código abaixo:
return Scaffold(
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
flexibleSpace: SafeArea(
child: Container(
padding: EdgeInsets.only(right: 16),
child: Row(
children: <Widget>[
IconButton(
onPressed: (){
Navigator.pop(context);
},
icon: Icon(Icons.arrow_back,color: Colors.black,),
),
SizedBox(width: 2,),
CircleAvatar(
backgroundImage: NetworkImage("<https://randomuser.me/api/portraits/men/5.jpg>"),
maxRadius: 20,
),
SizedBox(width: 12,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Kriss Benwat",style: TextStyle( fontSize: 16 ,fontWeight: FontWeight.w600),),
SizedBox(height: 6,),
Text("Online",style: TextStyle(color: Colors.grey.shade600, fontSize: 13),),
],
),
),
Icon(Icons.settings,color: Colors.black54,),
],
),
),
),
),
body: Container()
);
Isso nos dará o seguinte resultado conforme mostrado no emulador abaixo:

Como implementar a caixa de texto inferior
Na parte inferior da tela de detalhes do bate-papo, precisamos adicionar uma seção de mensagens, que conterá um editor de texto e um botão para enviar a mensagem.
Para isso, vamos usar o widget Align
e alinhar o filho dentro do widget com a parte inferior da tela. O código geral é fornecido no trecho de código abaixo:
body: Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomLeft,
child: Container(
padding: EdgeInsets.only(left: 10,bottom: 10,top: 10),
height: 60,
width: double.infinity,
color: Colors.white,
child: Row(
children: <Widget>[
GestureDetector(
onTap: (){
},
child: Container(
height: 30,
width: 30,
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(30),
),
child: Icon(Icons.add, color: Colors.white, size: 20, ),
),
),
SizedBox(width: 15,),
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: "Write message...",
hintStyle: TextStyle(color: Colors.black54),
border: InputBorder.none
),
),
),
SizedBox(width: 15,),
FloatingActionButton(
onPressed: (){},
child: Icon(Icons.send,color: Colors.white,size: 18,),
backgroundColor: Colors.blue,
elevation: 0,
),
],
),
),
),
],
),
Isso nos dará uma seção de mensagens com um campo de texto para digitar as mensagens e um botão para enviar as mensagens:

Também temos um botão à esquerda que podemos usar para adicionar outras opções de menu para mensagens.
Como configurar a seção de lista de mensagens na tela do bate-papo
Agora, vamos criar a interface do usuário para as mensagens que aparecem na tela de detalhes do bate-papo.
Primeiro, precisamos criar um modelo que reflita o objeto de instância da mensagem.
Para isso, precisamos criar um arquivo chamado chatMessageModel.dart dentro da pasta ./models e definir o objeto classe do seguinte modo:
import 'package:flutter/cupertino.dart';
class ChatMessage{
String messageContent;
String messageType;
ChatMessage({@required this.messageContent, @required this.messageType});
}
O objeto de classe aceita o conteúdo da mensagem e o tipo de mensagem (seja do remetente, seja do destinatário) como valores de instância.
Agora no chatDetailPage.dart, precisamos criar uma lista de mensagens a serem exibidas conforme o trecho de código abaixo:
List<ChatMessage> messages = [
ChatMessage(messageContent: "Hello, Will", messageType: "receiver"),
ChatMessage(messageContent: "How have you been?", messageType: "receiver"),
ChatMessage(messageContent: "Hey Kriss, I am doing fine dude. wbu?", messageType: "sender"),
ChatMessage(messageContent: "ehhhh, doing OK.", messageType: "receiver"),
ChatMessage(messageContent: "Is there any thing wrong?", messageType: "sender"),
];
Em seguida, vamos criar uma exibição de lista para as mensagens no topo dos filhos do widget Stack
, acima do widget Align
, conforme mostrado no trecho de código abaixo:
body: Stack(
children: <Widget>[
ListView.builder(
itemCount: messages.length,
shrinkWrap: true,
padding: EdgeInsets.only(top: 10,bottom: 10),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index){
return Container(
padding: EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10),
child: Text(messages[index].messageContent),
);
},
),
Align(
Agora as mensagens aparecerão em forma de lista conforme mostrado na captura de tela do emulador abaixo:

Temos as mensagens aparecendo na tela, mas elas não estão estilizadas da maneira que queremos na tela de bate-papo.
Como estilizar e posicionar as mensagens com base no remetente e no destinatário
Passamos, então a estilizar a lista de mensagens para que apareça como um balão de mensagem de bate-papo. Também vamos posicioná-los com base no tipo de mensagem usando o widget Align
, conforme mostrado no trecho de código abaixo:
ListView.builder(
itemCount: messages.length,
shrinkWrap: true,
padding: EdgeInsets.only(top: 10,bottom: 10),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index){
return Container(
padding: EdgeInsets.only(left: 14,right: 14,top: 10,bottom: 10),
child: Align(
alignment: (messages[index].messageType == "receiver"?Alignment.topLeft:Alignment.topRight),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: (messages[index].messageType == "receiver"?Colors.grey.shade200:Colors.blue[200]),
),
padding: EdgeInsets.all(16),
child: Text(messages[index].messageContent, style: TextStyle(fontSize: 15),),
),
),
);
},
),
Isso nos dará o seguinte resultado conforme mostrado no emulador abaixo:

Você pode ver uma demonstração geral de toda a interface do usuário da aplicação abaixo:

Parabéns! Criamos uma interface de usuário de aplicação de bate-papo intuitiva e moderna inteiramente no ecossistema Flutter e Dart.
Recapitulando
As aplicações de mensagens sociais são um meio de comunicação essencial hoje em dia. Equipados com interfaces de bate-papo poderosas e de última geração com chamadas de áudio e vídeo, anexos de imagens e arquivos e muito mais, essas aplicações de bate-papo tornaram a comunicação muito mais eficiente. Elas tornaram o mundo menor para nós.
O objetivo principal deste artigo foi mostrar como desenvolver uma interface de usuário simples e intuitiva para aplicações de bate-papo com um design moderno no ecossistema do Flutter. A implementação passo a passo forneceu uma demonstração detalhada da interface do usuário da aplicação, bem como uma visão geral do ambiente de codificação do Flutter.
Espero que este tutorial o ajude a criar sua próxima aplicação de bate-papo usando o Flutter.
Você também pode obter inspiração sobre a interface de usuário de aplicação de bate-papo e sobre o desenvolvimento dos recursos dos principais modelos de aplicação de bate-papo do Flutter disponíveis no mercado. Certifique-se de conferi-los também.