Apr 22, 2021
This tutorial will show you how you can use Weavy's conversation API to build a Xamarin.Forms chat app with some of Telerik's awesome UI components. Please note that the end result of this tutorial is not a ready-to-use, fully-featured chat app, but instead, a showcase of what you can do with Weavy and external component providers such as Telerik.
The app we are going to build will have the following functionallity:
*These features are available in the repo below. In this tutorial we'll go over the fundamentals and some basic features.
The repo is available at https://github.com/weavy/weavy-telerik-xamarin-chat. Feel free to clone, add, modify and use it as you like. Again, this is only a show case app and not intended for production. Use it as a guide when building your own chat app :)
In this project, we are going to use the following Telerik Xamarin UI components:
Telerik component | Used for |
---|---|
ListView | List the conversations |
AutoCompleteView | Display Weavy users when creating a new conversation |
Conversational UI | Display and send messages in a conversation |
For this app we will be using MVVM Cross, which is a MVVM pattern framework that you can use together will Xamarin.Forms. For more information and details, take a look at https://www.mvvmcross.com/. Don't worry, if you have used the MVVM pattern before with Xamarin.Forms, you should be familiar even though you havn't used MVVM Cross.
Ok, let's start the step by step tutorial. Please note that we will not cover every little detail in this guide. For the complete show case app, please refer to the Github repo.
If you haven't yet installed the MVVM Cross VS template mentioned above, do it now.
We need some settings that we are going to use in the app. For this purpose we just create a static helper class to hold the values we need. In a real world scenario, you should store these settings in a secure storage provider which is available for each platform and Xamarin.Forms.
Create the following Constants.cs
class in the [YourProjectName].Core
project
public class Constants {
// feel free to try out this chat app against the test site https://showcase.weavycloud.com/. We do recommend that you set up your own Weavy though. Take a look at https://docs.weavy.com for more information..
public static string RootUrl = "https://showcase.weavycloud.com/";
public static User Me { get; internal set; }
}
RootUrl
above to your url. Want to know how to setup Weavy? Check out this article: Get started with the Server SDKIn order to make the API calls to Weavy we need a Rest service to handle the requests. Each api request also need a Bearer token that authenticates the user against Weavy. The Rest calls will be made in a class called RestService
and the token generation will be made in a class called TokenService
.
1. Create a new folder in the [YourProjectName].Core
called Services
. In that folder, create a new file called IRestService.cs
with the following content:
public interface IRestService {
Task<T> GetAsync<T>(string resource);
Task<HttpResponseMessage> PostAsync(string resource, string jsonData = "");
}
2. In the same folder, create a new file called ITokenService.cs
with the following content.
public interface ITokenService {
string GenerateToken();
}
Service
like we just did above, MVVM Cross will automatically wire up the corresponding implementation in the Dependency Container. Take a look in the App.cs
file for more information.3. Now it's time to add the classes for the interfaces above. In the same corresponding files (or if you prefer to create new ones), add the following implementation for the RestService
.
public class RestService : IRestService {
private readonly ITokenService _tokenService;
private HttpClient _client;
public RestService(ITokenService tokenService)
{
_tokenService = tokenService;
_client = new HttpClient()
{
BaseAddress = new Uri(Constants.RootUrl)
};
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _tokenService.GenerateToken());
}
public async Task<T> GetAsync<T>(string resource)
{
var response = await _client.GetAsync(resource);
var data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data);
}
public async Task<HttpResponseMessage> PostAsync(string resource, string jsonData = "")
{
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
return await _client.PostAsync(resource, content);
}
}
Now we have everything we need to call the Weavy Rest api that you will create later in this tutorial.
Add the following implementation of the ITokenService
interface.
public class TokenService : ITokenService {
public string GenerateToken()
{
// a ready to use, long lived demo token with a pre-defined user. This is only for demo purposes!
// This token only works if Constants.RootUrl is set to https://showcase.weavycloud.com/. If you are using a local/remote Weavy of your own,
// please take a look at https://docs.weavy.com/client/authentication on how to setup a Client and generate tokens
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJvbGl2ZXIiLCJuYW1lIjoiT2xpdmVyIFdpbnRlciIsImV4cCI6MjUxNjIzOTAyMiwiaXNzIjoic3RhdGljLWZvci1kZW1vIiwiY2xpZW50X2lkIjoiV2VhdnlEZW1vIiwiZGlyIjoiY2hhdC1kZW1vLWRpciIsImVtYWlsIjoib2xpdmVyLndpbnRlckBleGFtcGxlLmNvbSIsInVzZXJuYW1lIjoib2xpdmVyIn0.VuF_YzdhzSr5-tordh0QZbLmkrkL6GYkWfMtUqdQ9FM";
// generate a token for Weavy. In a real world appplication, this should based on the signed in user from your host system.
// check out the Weavy Docs (https://docs.weavy.com/client/authentication) how to create a jwt token and what different claims you can set.
// The ClientId and ClientSecret constants is the id and secret of the Authentication Client created in the Weavy installation you are using.
//return new JwtBuilder()
// .WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
// .WithSecret(Constants.ClientSecret)
// .AddClaim("exp", DateTimeOffset.UtcNow.AddMinutes(1).ToUnixTimeSeconds())
// .AddClaim("iss", Constants.ClientId)
// .AddClaim("sub", "1")
// .AddClaim("email", "johndoe@test.com")
// .AddClaim("name", "John Doe")
// .Encode();
}
}
Here we are using JWT.Net for the token generation. If you don't want to use the demo tokens (or if you are using your own local Weavy) and create real tokens, you have to add the following nuget package: https://www.nuget.org/packages/JWT/7.3.1
As noted in the code comments, in this scenario we are using a hard-coded JWT token for test purposes only. If you are building a real app, the authentication flow would be to first authenticate the user agains your own host system and then create a JWT token with the user's information. The token should then be sent to Weavy in the requests as the Bearer token in the Authorization header.
true/false
if the user is authenticated and then show the appropriate screen at startup.The pre-defined JWT tokens we have prepared for this tutorial/demo are defined below. Feel free to use these tokens for the https://showcase.weavycloud.com/ Weavy test site. This is the RootUrl
that needs to be set in the Constants.cs
class for this to work.
Oliver Winter
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJvbGl2ZXIiLCJuYW1lIjoiT2xpdmVyIFdpbnRlciIsImV4cCI6MjUxNjIzOTAyMiwiaXNzIjoic3RhdGljLWZvci1kZW1vIiwiY2xpZW50X2lkIjoiV2VhdnlEZW1vIiwiZGlyIjoiY2hhdC1kZW1vLWRpciIsImVtYWlsIjoib2xpdmVyLndpbnRlckBleGFtcGxlLmNvbSIsInVzZXJuYW1lIjoib2xpdmVyIn0.VuF_YzdhzSr5-tordh0QZbLmkrkL6GYkWfMtUqdQ9FM
Lilly Diaz
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsaWxseSIsIm5hbWUiOiJMaWxseSBEaWF6IiwiZXhwIjoyNTE2MjM5MDIyLCJpc3MiOiJzdGF0aWMtZm9yLWRlbW8iLCJjbGllbnRfaWQiOiJXZWF2eURlbW8iLCJkaXIiOiJjaGF0LWRlbW8tZGlyIiwiZW1haWwiOiJsaWxseS5kaWF6QGV4YW1wbGUuY29tIiwidXNlcm5hbWUiOiJsaWxseSJ9.rQvgplTyCAfJYYYPKxVgPX0JTswls9GZppUwYMxRMY0
Samara Kaur
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzYW1hcmEiLCJuYW1lIjoiU2FtYXJhIEthdXIiLCJleHAiOjI1MTYyMzkwMjIsImlzcyI6InN0YXRpYy1mb3ItZGVtbyIsImNsaWVudF9pZCI6IldlYXZ5RGVtbyIsImRpciI6ImNoYXQtZGVtby1kaXIiLCJlbWFpbCI6InNhbWFyYS5rYXVyQGV4YW1wbGUuY29tIiwidXNlcm5hbWUiOiJzYW1hcmEifQ.UKLmVTsyN779VY9JLTLvpVDLc32Coem_0evAkzG47kM
Adam Mercer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZGFtIiwibmFtZSI6IkFkYW0gTWVyY2VyIiwiZXhwIjoyNTE2MjM5MDIyLCJpc3MiOiJzdGF0aWMtZm9yLWRlbW8iLCJjbGllbnRfaWQiOiJXZWF2eURlbW8iLCJkaXIiOiJjaGF0LWRlbW8tZGlyIiwiZW1haWwiOiJhZGFtLm1lcmNlckBleGFtcGxlLmNvbSIsInVzZXJuYW1lIjoiYWRhbSJ9.c4P-jeQko3F_-N4Ou0JQQREePQ602tNDhO1wYKBhjX8
Now that we have a rest service and a way to generate valid JWT tokens, we need a service class to handle all the functionality for a Weavy conversation. This involves getting all the user's conversations, create a new conversation, getting the messages in a specific conversation, and so on.
1. Create a new interface called IConversationService.cs
in the Services
folder.
public interface IConversationService {
Task<IEnumerable<Conversation>> Get();
}
The method returns a list of Conversation
which we haven't yet created. So let's do that. Create a new folder in the [YourProjectName].Core
project called Models
. In that folder, create the following class.
public class Conversation {
public int Id { get; set; }
[JsonProperty("is_room")]
public bool IsRoom { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[JsonProperty("thumb")]
public string ThumbUrl { get; set; }
[JsonProperty("last_message")]
public Message LastMessage { get; set; }
public IEnumerable<Member> Members { get; set; }
[JsonProperty("is_read")]
public bool IsRead { get; set; }
[JsonProperty("is_pinned")]
public bool IsPinned { get; set; }
public string ThumbUrlFull => $"{Constants.RootUrl}{(ThumbUrl.Replace("{options}", "64"))}";
public string ConversationTitle => IsRoom ? Name ?? string.Join(", ", Members.Select(x => x.Name)) : Members.FirstOrDefault(x => x.Id != Constants.Me.Id)?.Name;
}
A conversation can have two or more Members
. Create a new class in the Models
folder called Member.cs
public class Member
{
public int Id { get; set; }
public string Username { get; set; }
public string Name { get; set; }
[JsonProperty("thumb")]
public string ThumbUrl { get; set; }
[JsonProperty("delivered_at")]
public DateTime DeliveredAt { get; set; }
[JsonProperty("read_at")]
public DateTime ReadAt { get; set; }
public Precence Precence { get; set; }
public string ThumbUrlFull => $"{Constants.RootUrl}{(ThumbUrl.Replace("{options}", "64"))}";
}
public enum Precence
{
Away = 0,
Active = 1
}
Now, let's create the implementation of the ConversationService
. Add the following code.
public class ConversationService : IConversationService {
private readonly IRestService _restService;
public ConversationService(IRestService restService) {
_restService = restService;
}
public async Task<IEnumerable<Conversation>> Get() {
return await _restService.GetAsync<IEnumerable<Conversation>>("/api/conversations");
}
}
In the code above, we are injecting the IRestService
to the ConversationService
. In the Get
method, we are doing a Get
request to fetch all the user's ongoing conversations from Weavy. The /api/conversations
endpoint in Weavy is not created yet. In Weavy, you can add any API endpoints you want and need. We'll take a look at that later in this tutorial.
MVVM Cross automatically wires up the Views (Pages) and the corresponding ViewModel. Now it's time to create the home page where we are going to list the user's ongoing conversations.
1. Create a new folder in the [YourProjectName].Core
project's ViewModels folder called Home
. In that folder, create a new class called HomeViewModel.cs
. Make sure the class inherits from BaseViewModel
.
public class HomeViewModel : BaseViewModel {
...
}
Now, let's use the ConversationService
we created in the previous step. We use the service to get all the user's ongoing conversations in Weavy and put them in a Telerik ListView control.
public class HomeViewModel : BaseViewModel {
private readonly IConversationService _conversationService;
public HomeViewModel(IConversationService conversationService){
_conversationService = conversationService;
}
private ObservableCollection<ConversationItem> _list;
public ObservableCollection<ConversationItem> List
{
get => _list;
set => SetProperty(ref _list, value);
}
public override void Prepare() {
// load conversation list
Task.Run(async () => await LoadConversations()).Wait();
}
private async Task LoadConversations() {
List.Clear();
var conversations = await _conversationService.Get();
if(conversations != null && conversations.Count() > 0) {
foreach (var conversation in conversations) {
List.Add(new ConversationItem {
Id = conversation.Id,
Name = conversation.Name,
IsRoom = conversation.IsRoom,
IsRead = conversation.IsRead,
ConversationTitle = conversation.ConversationTitle,
LastMessageAt = conversation.LastMessage.CreatedAt,
LastMessageByName = conversation.LastMessage.CreatedBy.Id == Constants.Me.Id ? "Me: " : (conversation.IsRoom ? $"{conversation.LastMessage.CreatedBy.Name.Split(' ')[0]}: " : ""),
Description = conversation.LastMessage.Text,
ThumbUrl = conversation.ThumbUrlFull
});
}
}
}
}
The ConversationItem
class is a ViewModel for a Conversation
entity that is more suitable for the View than the core model. The view model has bindable properties which are necessary to make the View (Page) reactive to changes in the viewmodel. For example, if the IsRead
flag changes on a specific conversation, we want the UI to update accordingly. Add the ConversationItem
class to the ViewModels
folder.
public class ConversationItem : BaseViewModel {
private int _id;
public int Id {
get => _id;
set => SetProperty(ref _id, value);
}
private string _description;
public string Description {
get => _description;
set => SetProperty(ref _description, value);
}
private DateTime _lastMessageAt;
public DateTime LastMessageAt {
get => _lastMessageAt;
set => SetProperty(ref _lastMessageAt, value);
}
private string _name;
public string Name {
get => _name;
set => SetProperty(ref _name, value);
}
public ImageSource ImageSource => new UriImageSource() { Uri = new Uri(ThumbUrl) };
private string _thumbUrl;
public string ThumbUrl {
get => _thumbUrl;
set => SetProperty(ref _thumbUrl, value);
}
public string ConversationTitle { get; internal set; }
private string _lastMessageByName;
public string LastMessageByName {
get => _lastMessageByName;
set => SetProperty(ref _lastMessageByName, value);
}
private bool _isRead;
public bool IsRead {
get => _isRead;
set => SetProperty(ref _isRead, value);
}
public bool IsRoom { get; internal set; }
}
Ok, now that we have our view model, we need a Page to show it on. All the Views are created in the [YourProjectName].UI
project. In the Pages
folder, add a new Content Page and name it HomePage.xaml
. Replace the code in the .xmal file with:
<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage x:TypeArguments="viewModels:HomeViewModel"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
xmlns:local="clr-namespace:WeavyTelerikChat.UI.Pages"
xmlns:localviews="clr-namespace:WeavyTelerikChat.UI.Views"
xmlns:telerikDataControls="clr-namespace:Telerik.XamarinForms.DataControls;assembly=Telerik.XamarinForms.DataControls"
xmlns:telerikListView="clr-namespace:Telerik.XamarinForms.DataControls.ListView;assembly=Telerik.XamarinForms.DataControls"
xmlns:telerikListViewCommands="clr-namespace:Telerik.XamarinForms.DataControls.ListView.Commands;assembly=Telerik.XamarinForms.DataControls"
x:Class="WeavyTelerikChat.UI.Pages.HomePage"
xmlns:viewModels="clr-namespace:WeavyTelerikChat.Core.ViewModels.Home;assembly=WeavyTelerikChat.Core"
Title="Messenger">
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand">
<telerikDataControls:RadListView x:Name="listView"
ItemsSource="{Binding List}"
SelectionMode="Single"
SelectedItem="{mvx:MvxBind ItemSelected}">
<telerikDataControls:RadListView.Commands>
<telerikListViewCommands:ListViewUserCommand
Id="ItemTap"
Command="{Binding TappedCommand}" />
</telerikDataControls:RadListView.Commands>
<telerikDataControls:RadListView.ItemTemplate>
<DataTemplate>
<telerikListView:ListViewTemplateCell>
<telerikListView:ListViewTemplateCell.View>
<localviews:ConversationCell />
</telerikListView:ListViewTemplateCell.View>
</telerikListView:ListViewTemplateCell>
</DataTemplate>
</telerikDataControls:RadListView.ItemTemplate>
</telerikDataControls:RadListView>
</StackLayout>
</ContentPage.Content>
</views:MvxContentPage>
MyProject
in the .xaml file above with the name of your project.Replace the generated HomePage.xaml.cs
with:
namespace MyProject.UI.Pages {
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxContentPagePresentation(WrapInNavigationPage = true)]
public partial class HomePage : MvxContentPage<HomeViewModel> {
public HomePage() {
InitializeComponent();
}
protected override void OnAppearing() {
base.OnAppearing();
if (Application.Current.MainPage is NavigationPage navigationPage) {
navigationPage.BarTextColor = Color.White;
navigationPage.BarBackgroundColor = (Color)Application.Current.Resources["PrimaryColor"];
}
}
}
}
MyProject
in the .xaml.cs file above with the name of your project.Weavy comes with a bunch of existing API endpoints that can come in handy when building either apps for the web or mobile. Weavy also lets you add any API endpoint you want and need for any special needs. At the time of writing, no pre-defined API endpoints for the Conversation and Messages exist when you install and run your Weavy instance.
It's pretty easy to add your own API endpoints. Open up the Weavy solution in Visual Studio and create a new API Controller under the Areas/Api/Controllers
folder and name it ConversationsController.cs
. Add the following code.
namespace Weavy.Areas.Api.Controllers {
/// <summary>
/// Api controller for manipulating Conversations.
/// </summary>
[RoutePrefix("api")]
public class ConversationsController : WeavyApiController {
/// <summary>
/// Get all <see cref="Conversation" /> for the current user.
/// </summary>
/// <example>GET /api/conversations</example>
/// <returns>The users conversations.</returns>
[HttpGet]
[ResponseType(typeof(IEnumerable<Conversation>))]
[Route("conversations")]
public IHttpActionResult List() {
var conversations = ConversationService.Search(new ConversationQuery());
return Ok(conversations);
}
}
}
That's it! Well, that was pretty easy... ;) Compile the Weavy solution and make sure the updates are fully functional by going to https://[yourweavyurl]/api/conversations
. Your active conversations should be returned in JSON format.
This section depends if you are using the pre-defined Weavy demo installation at https://showcase.weavycloud.com/
or if you have set up a local Weavy.
https://showcase.weavycloud.com/
Make sure Android is set as the start-up project and hit Start! The Android emulator should start up with your app. Since you are using the pre-defined JWT tokens from Step 3, the user will be authenticated and the user's conversations will show up (if any)
Make sure Android is set as the start-up project and hit Start! The Android emulator should start up with your app.
Make sure you are using a valid JWT token. For more information on how to set up and create the token, please refer to this article: Authentication. The article describes how to authenticate from the Weavy client, but the same goes for any external app such as this one. The steps consist of:
When the apps is running, you will notice that there are no conversations in the list. This is because the user is created in Weavy the first time it's authenticated. We don't yet have any functionality to create a new one either. But don't worry, take a look at the Next steps below how you could continue.
The showcase app at https://github.com/weavy/weavy-telerik-xamarin-chat has a lot more features beyond the scope of this tutorial. These features include:
Select what technology you want to ask about
Get answers faster with our tailored generative AI. Learn more about how it works
This is a custom LLM for answering your questions. Answers are based on the contents of the documentation and this feature is experimental.
To access live chat with our developer success team you need a Weavy account.