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:

image-105_1

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:

image-106

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:

image-107

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:

image-108

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:

image-109

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:

image-110

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:

image-111

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:

2021-01-20-13.40.11

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:

image-112

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:

image-113

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:

image-114

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:

image-115

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

2021-01-20-13.52.45

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.