Olá, neste post irei demonstrar como você pode construir um chat utilizando Xamarin.Forms.
Irei partir do princípio que você acabou de criar um projeto Xamarin.Forms do tipo Portable Class Library (PCL), caso possua alguma dúvida sobre isso, recomendo ler o post Criando um projeto Xamarin.Forms.
Estruturando o projeto
Para este projeto, utilize o padrão MVVM (Model View ViewModel), dessa forma crie uma estrutura de pastas no seu projeto portable, como a demonstrada a seguir.
Crie também uma pasta CustomCells, que servirá para adicionar algumas ViewCell’s.
ADICIONANDO O NUGET PACKAGE
Para auxiliar na utilização do padrão MVVM, adicione o nuget package MvvmHelpers.
Clique com o botão direito em cima de sua Solution e selecione “Manage NuGet Packages for Solution…”.
Digite “Refractored.MvvmHelpers” e selecione o plugin como demonstrado na imagem a seguir.
Selecione todos os projetos e clique no botão “Install”.
Models
Dentro da pasta Models crie uma classe chamada Message.
A classe Message terá todas as propriedades necessárias da mensagem, neste caso suas propriedades serão:
- Text: O texto da mensagem.
- MessageDateTime: A data da mensagem.
- TimeDisplay: Receberá a data da mensagem formatada.
- IsTextIn: Flag para indicar se a mensagem está sendo recebida (True) ou enviada (False).
Observe que a classe Message herda da classe ObservableObject que encontra-se no MvvmHelpers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using MvvmHelpers; | |
namespace DemoChat.Models | |
{ | |
public class Message : ObservableObject | |
{ | |
public string Text | |
{ | |
get { return _text; } | |
set { SetProperty(ref _text, value); } | |
} | |
string _text; | |
public DateTime MessageDateTime | |
{ | |
get { return _messageDateTime; } | |
set { SetProperty(ref _messageDateTime, value); } | |
} | |
DateTime _messageDateTime; | |
public string TimeDisplay => MessageDateTime.ToLocalTime().ToString(); | |
public bool IsTextIn | |
{ | |
get { return _isTextIn; } | |
set { SetProperty(ref _isTextIn, value); } | |
} | |
bool _isTextIn; | |
} | |
} |
ViewModels
Dentro da pasta ViewModels crie uma classe chamada MainPageViewModel.
A classe MainPageViewModel irá conter as seguintes propriedades.
- ListMessages: Lista contendo todas as mensagens.
- SendCommand: Comando que será executado quando o usuário clicar no botão enviar.
- OutText: Texto digitado pelo usuário.
Observe que a classe MainPageViewModel herda de BaseViewModel que encontra-se no MvvmHelpers e as propriedades ListMessages e SendCommand estão sendo instanciadas no método construtor.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Globalization; | |
using System.Windows.Input; | |
using DemoChat.Models; | |
using MvvmHelpers; | |
using Xamarin.Forms; | |
namespace DemoChat.ViewModels | |
{ | |
public class MainPageViewModel : BaseViewModel | |
{ | |
public ObservableRangeCollection<Message> ListMessages { get; } | |
public ICommand SendCommand { get; set; } | |
public MainPageViewModel() | |
{ | |
ListMessages = new ObservableRangeCollection<Message>(); | |
SendCommand = new Command(() => | |
{ | |
if (!String.IsNullOrWhiteSpace(OutText)) | |
{ | |
var message = new Message | |
{ | |
Text = OutText, | |
IsTextIn = false, | |
MessageDateTime = DateTime.Now | |
}; | |
ListMessages.Add(message); | |
OutText = ""; | |
} | |
}); | |
} | |
public string OutText | |
{ | |
get { return _outText; } | |
set { SetProperty(ref _outText, value); } | |
} | |
string _outText = string.Empty; | |
} | |
} |
CustomCells
Dentro da pasta CustomCells, crie uma classe chamada SelectorDataTemplate e duas ViewCell, uma chamada de TextInViewCell e a outra TextOutViewCell.
TextInViewCell
A TextInViewCell servira para apresentar a mensagem que chega para o usuário. Ela terá um Frame contendo uma Label com o texto e uma Label para apresentar a Data da mensagem.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8" ?> | |
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
x:Class="DemoChat.CustomCells.TextInViewCell"> | |
<Grid ColumnSpacing="2" Padding="5"> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="2"></ColumnDefinition> | |
<ColumnDefinition Width="Auto"></ColumnDefinition> | |
<ColumnDefinition Width="*"></ColumnDefinition> | |
</Grid.ColumnDefinitions> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="*"></RowDefinition> | |
<RowDefinition Height="Auto"></RowDefinition> | |
</Grid.RowDefinitions> | |
<Frame Grid.Row="0" Grid.Column="1" BackgroundColor="#0535f0" CornerRadius="15"> | |
<Frame.HasShadow> | |
<OnPlatform x:TypeArguments="x:Boolean" iOS="false" Android="true"/> | |
</Frame.HasShadow> | |
<StackLayout> | |
<Label TextColor="White" Text="{Binding Text}" /> | |
</StackLayout> | |
</Frame> | |
<Label FontSize="Micro" Grid.Row="1" Grid.Column="1" Text="{Binding MessageDateTime, StringFormat='{0:MM/dd/yyyy hh:mm tt}'}" TextColor="Gray"></Label> | |
</Grid> | |
</ViewCell> |
TextOutViewCell
A TextOutViewCell servira para apresentar a mensagem que é enviada pelo usuário. Ela terá um Frame contendo uma Label com o texto e uma Label para apresentar a Data da mensagem.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
x:Class="DemoChat.CustomCells.TextOutViewCell"> | |
<Grid ColumnSpacing="2" Padding="5"> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="*"></ColumnDefinition> | |
<ColumnDefinition Width="Auto"></ColumnDefinition> | |
<ColumnDefinition Width="2"></ColumnDefinition> | |
</Grid.ColumnDefinitions> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="*"></RowDefinition> | |
<RowDefinition Height="Auto"></RowDefinition> | |
</Grid.RowDefinitions> | |
<Frame Grid.Row="0" Grid.Column="1" CornerRadius="15"> | |
<Frame.HasShadow> | |
<OnPlatform x:TypeArguments="x:Boolean" Android="true" iOS="false"/> | |
</Frame.HasShadow> | |
<Frame.BackgroundColor> | |
<OnPlatform x:TypeArguments="Color" Android="White" iOS="#F5F5F5"/> | |
</Frame.BackgroundColor> | |
<StackLayout> | |
<Label TextColor="Black" Text="{Binding Text}" /> | |
</StackLayout> | |
</Frame> | |
<Label Grid.Row="1" FontSize="Micro" Grid.Column="1" HorizontalTextAlignment="End" Text="{Binding MessageDateTime, StringFormat='{0:MM/dd/yyyy hh:mm tt}'}" TextColor="Gray"></Label> | |
</Grid> | |
</ViewCell> |
SelectorDataTemplate
A classe SelectorDataTemplate servirá para identificar quando a mensagem está chegando ou sendo enviada, e no método OnSelectTemplate irá determinar qual ViewCell será usada.
Observe que a classe SelectorDataTemplate herda de DataTemplateSelector que encontra-se no Xamarin.Forms.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DemoChat.CustomCells; | |
using DemoChat.Models; | |
using Xamarin.Forms; | |
namespace DemoChat | |
{ | |
public class SelectorDataTemplate : DataTemplateSelector | |
{ | |
private readonly DataTemplate textInDataTemplate; | |
private readonly DataTemplate textOutDataTemplate; | |
protected override DataTemplate OnSelectTemplate(object item, BindableObject container) | |
{ | |
var messageVm = item as Message; | |
if (messageVm == null) | |
return null; | |
return messageVm.IsTextIn ? this.textInDataTemplate : this.textOutDataTemplate; | |
} | |
public SelectorDataTemplate() | |
{ | |
this.textInDataTemplate = new DataTemplate(typeof(TextInViewCell)); | |
this.textOutDataTemplate = new DataTemplate(typeof(TextOutViewCell)); | |
} | |
} | |
} |
Views
Dentro da pasta Views crie uma ContentPage
Observação: Não se esqueça de alterar o arquivo App, para que a MainPage seja instanciada corretamente.
MainPage Xaml
A MainPage deverá conter:
- SelectorDataTemplate no ResourceDictionary.
- ListView para apresentar as mensagens.
- Entry para o usuário escrever um texto.
- Button/Image para o usuário clicar quando quiser enviar.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8" ?> | |
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
xmlns:local="clr-namespace:DemoChat;assembly=DemoChat" | |
x:Class="DemoChat.Views.MainPage" Title="Chat"> | |
<ContentPage.Resources> | |
<ResourceDictionary> | |
<local:SelectorDataTemplate x:Key="MessageTemplateSelector"/> | |
</ResourceDictionary> | |
</ContentPage.Resources> | |
<ScrollView> | |
<Grid RowSpacing="0" ColumnSpacing="0" > | |
<Grid.RowDefinitions> | |
<RowDefinition Height="*"/> | |
<RowDefinition Height="Auto" /> | |
</Grid.RowDefinitions> | |
<ListView | |
x:Name="MessagesListView" | |
ItemTemplate="{StaticResource MessageTemplateSelector}" | |
ItemsSource="{Binding ListMessages}" | |
HasUnevenRows="True" SeparatorVisibility="None" IsEnabled="True" Grid.Row="0"/> | |
<StackLayout Orientation="Horizontal" Grid.Row="1" BackgroundColor="White" VerticalOptions="EndAndExpand"> | |
<Entry | |
HorizontalOptions="FillAndExpand" | |
Placeholder="Message" | |
Text="{Binding OutText}" Keyboard="Chat" Margin="4"/> | |
<Image Source="sendButton.png" WidthRequest="40" HeightRequest="40" Margin="4"> | |
<Image.GestureRecognizers> | |
<TapGestureRecognizer | |
Command="{Binding SendCommand}" /> | |
</Image.GestureRecognizers> | |
</Image> | |
</StackLayout> | |
</Grid> | |
</ScrollView> | |
</ContentPage> |
MainPage Code-Behind
No método construtor instancie uma MainPageViewModel, atribua ao BindingContext e em seguida defina o Scroll para o final da lista no evento CollectionChanged, como demonstrado a seguir.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DemoChat.ViewModels; | |
using Xamarin.Forms; | |
namespace DemoChat.Views | |
{ | |
public partial class MainPage | |
{ | |
MainPageViewModel vm; | |
public MainPage() | |
{ | |
InitializeComponent(); | |
BindingContext = vm = new MainPageViewModel(); | |
vm.ListMessages.CollectionChanged += (sender, e) => | |
{ | |
var target = vm.ListMessages[vm.ListMessages.Count – 1]; | |
MessagesListView.ScrollTo(target, ScrollToPosition.End, true); | |
}; | |
} | |
} | |
} |
Resultado
Esse e todos os exemplos deste blog encontram-se disponíveis no GitHub.
Bom dia! Tenho uma aplicação que envia e recebe cadastros para um banco de dados em um servidor online. O banco de dados usado é firebird 2.5 e o processo de envio e recepção dos dados é via Json. Gostaria de saber se existe a possibilidade de enviar essas mensagens para ele e baixar dele quando o usuário estiver conectado. Neste caso, não preciso que a conversa seja no stilo realtime, pode até ser, mas para o meu caso, não tem necessidade. Só preciso que quando o usuário se conectar no aplicativo, o server consiga listar as mensagens enviadas por outro usuário chegue pra ele, respeitando a ordem das conversas, no mesmo esquema do exemplo citado acima. Aceito sugestões e idéias.
CurtirCurtir
Olá Eduardo,
Como você disse que não precisa ser realtime e você já possui uma comunicação com o banco de dados, acredito que fique fácil para você.
O que você precisa fazer é quando o usuário se conectar com a internet, baixar as mensagens para ele. E no app você adiciona em uma list e ordena pela data. Para ficar mais preciso utilize a Data, hora, minuto e segundo.
Espero ter ajudado 🙂
CurtirCurtir
Boa tarde, muito bom o post !!
Uma dúvida…pra uma estrutura de chat mesmo, tipo whatsapp, é recomendavel que criar um serviço com SignalR ou acha que só uma Api + Database serve ? Quando penso sobre, acho que se a aplicação não precisar ser tempo real (bem fidedigno tipo Whatsapp) serve uma Api + Db…mas se precisar ser tempo real, é aconselhavel um SignalR ne…
Um cliente meu ta interessado em um chat e estou dando uma estudada.
CurtirCurtir
Olá Marcelo, obrigado !!
Bom… acho que você mesmo acabou respondendo a sua pergunta kkk
Se esse chat for mais algo como para demonstrar algumas observações, uma Api + DB acredito ser o suficiente. Caso precise de algo mais complexo, acho que só isso não resolva o seu problema.
Espero ter ajudado.
CurtirCurtir