Xamarin.Forms + Bot Framework + MVVM
With Microsoft’s Botframework, you can build a chat bot which runs on several platforms (Social media, websites…). Now think about Xamarin.Forms with it, you can build apps which run on several platforms too (iOS, Android, UWP…). Thanks to the DirectLineClient, we can build custom clients for our bots. In our case, the client will be a Xamarin.Forms app. (meaning your bot can be available on iOS, UWP, Android… and also Facebook, Skype Telegram…) This means you can be everywhere with just a bit of code and this is Awesome!
Our main objective
Our main objective in this tutorial is to build a simple Xamarin.Forms application which will communicate with an already existing chat bot (built with the Botframework and hosted on Azure). All that using the MVVM design pattern.
Here is the source code for this tutorial.
Don’t miss any post and subscribe to the mailing list
[zc4wp_za2]
What we will be doing.
Accessing direct-line with the new Azure Bot service
Recently, Microsoft created the bot service and now the creation of chat bots using its bot framework has been moved to Azure so, we will access our bot through Azure. After accessing or creating your bot, we will configure direct-line channel (The channel through which our bot will communicate with the mobile app.) and get the direct-line secrets.
- After accessing you bot on azure, chose the Channels option of your bot on the options blade.
- On the next blade which will display, under the Add features channel options, select direct-line.
- Create your secrets.
- Take down your bot handle.
Let’s build the Xamarin.Forms client
We will build the Xamarin.Forms app which will use the DirectLineClient to communicate with the bot. Remenber we will use the MVVM architectural design pattern to build this app, that is why we will divide the task of building the app into several components Model, View, View-Model and the Service.
First, install this package to your solution : Microsoft.Bot.Connector.DirectLine
[zc4wp_za2]
Model
Let’s start with the Model. Our model will be a representation of the message sent by the Bot or by the Client. In reality, what I call Messages is just an abstraction of “An Activity” sent or received by the Bot. There are several types of activities, (see the botframework documentation https://docs.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-activities) but we are interested here in the message activity in its simplest form.
- Create a folder in your project directory called “Models”
- Add a new class “BotMessage”
Here is the code for the model
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class BotMessage { public string ActivityId { get; set; } public DateTime Time { get; set; } public string Content { get; set; } public BotMessage(string activityId, string msg, DateTime time) { ActivityId = activityId; Time = time; Content = msg; } } |
This is for the simplest messages exchanged by the Bot and the client. Messages containing attachments such as images are not covered in this tutorial now and may be covered in another tutorial but from here implementing it as an additional functionality is not difficult.
The Service
Our service will communicate with the Direct Line, it will be in charge of:
- Sending Messages to the bot
- Receiving Messages from the bot
- Abstract the activity into simple messages. (Remember we receive messages in Activities from which we will get only the attributes and present it to the user).
The service will be used by the ViewModel to communicate with the Bot and produce a BotMessage model which will be represented in the View latter.
NB: The View Model does not contain any view component this is very important to note.
Let’s Describe how the service will send a message:
- We need to instantiate the DirectLineClient first, this object will be responsible for communicating with the Bot this is done by passing it the DirectLine Secret which we took online earlier in the tutorial.
- We Setup the DirectLineClient to initiate a conversation with the Bot, and each conversation initiated has a unique ID. So, we will save this conversation created in a Property BotConversation messages will be sent to and received from this conversation later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public BotConnectorService() { Client = new DirectLineClient(directLineSecret); } public async Task SetUpAsync() { if (!started) { BotConversation = await Client.Conversations .StartConversationAsync().ConfigureAwait(false); started = true; } } |
Remember what we said earlier, messages are sent to and from the bot using Activities, so we need to create an Activity to send a message to the Bot, the activity contains the message and sender info, after creating this, we send the message asynchronously to the bot using the DirectLineClient and remember we have initiated a conversation, and it is with its ID that we will send the message to the bot.
After sending the message, we call the method to receive messages immediately, cause we know the bot will reply to us.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public async Task SendMessageAsync(string messge, string userName = "") { var userMesage = new Activity() { From = new ChannelAccount(userName), Text = messge, Type = ActivityTypes.Message }; await Client.Conversations.PostActivityAsync(BotConversation.ConversationId, userMesage) .ConfigureAwait(false); //We initiate the process of receiving messages //From the Bot. await ReceiveMessageAsync(); } |
Let’s describe how messages are received
The message will be received using the DirectLineClient, and the id of the conversation which we initiated earlier. Using the following code snippet.
1 2 | var response = await Client.Conversations.GetActivitiesAsync( - BotConversation.ConversationId, WaterMark).ConfigureAwait(false); |
The messages are sent to the response in the form of activities, we need to get only the activities which are from the bot, what will be use here is the Bot Handle (Which We took earlier from our bot on azure).
We then turn the activities into the simple BotMessage model using the method
1 | CreateBotMessages(activities) |
Then we invoke an event which will fire containing the messages from the bot, every subscriber will get these messages when it arrives.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public async Task ReceiveMessageAsync() { //Send the message to the bot var response = await Client.Conversations.GetActivitiesAsync( BotConversation.ConversationId, WaterMark).ConfigureAwait(false); WaterMark = response.Watermark; var activities = new List<Activity>(); foreach (var activity in response.Activities) { //Get only the activities corresponding to the Bot. if (activity.From.Id == BOT_HANDLE) activities.Add(activity); } //Fire the event for message received. BotMessageReceived?.Invoke(CreateBotMessages(activities)); } private List<BotMessage> CreateBotMessages(IEnumerable<Activity> activities) { var botMessages = new List<BotMessage>(); foreach (var activity in activities) { botMessages.Add(new BotMessage() { ActivityId = activity.Id, Content = activity.Text, ISent = false }); } return botMessages; } |
Here is the complete code for the BotConnectorService Class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | public class BotConnectorService { public static Conversation BotConversation { get; set; } public static DirectLineClient Client { get; set; } public const string BOT_HANDLE = ""; string directLineSecret = ""; private static string WaterMark { get; set; } private static bool started; public event Action<List<BotMessage>> BotMessageReceived; public BotConnectorService() { Client = new DirectLineClient(directLineSecret); } public async Task SetUpAsync() { if (!started) { BotConversation = await Client.Conversations .StartConversationAsync().ConfigureAwait(false); started = true; } } /// <summary> /// Get message from the BOT /// </summary> /// <returns></returns> public async Task ReceiveMessageAsync() { //Send the message to the bot var response = await Client.Conversations.GetActivitiesAsync( BotConversation.ConversationId, WaterMark).ConfigureAwait(false); WaterMark = response.Watermark; var activities = new List<Activity>(); foreach (var activity in response.Activities) { //Get only the activities corresponding to the Bot. if (activity.From.Id == BOT_HANDLE) activities.Add(activity); } //Fire the event for message received. BotMessageReceived?.Invoke(CreateBotMessages(activities)); } /// <summary> /// CHange message into Bot message. /// </summary> /// <param name="activities"></param> /// <returns></returns> private List<BotMessage> CreateBotMessages(IEnumerable<Activity> activities) { var botMessages = new List<BotMessage>(); foreach (var activity in activities) { botMessages.Add(new BotMessage() { ActivityId = activity.Id, Content = activity.Text, ISent = false }); } return botMessages; } /// <summary> /// Sends a message to the Bot. /// </summary> /// <param name="messge"></param> /// <param name="userName"></param> /// <param name="userId"></param> /// <returns></returns> public async Task SendMessageAsync(string messge, string userName = "") { var userMesage = new Activity() { From = new ChannelAccount(userName), Text = messge, Type = ActivityTypes.Message }; await Client.Conversations.PostActivityAsync(BotConversation.ConversationId, userMesage) .ConfigureAwait(false); //We initiate the process of receiving messages //From the Bot. await ReceiveMessageAsync(); } } |
The ViewModel
The ViewModel is responsible for calling the service, and receiving the messages sent and received into a data structure which will be represented in the View.
To create the ViewModel, we will use a set of helpers which are available here.
The ViewModel will have the BotConnectorService object, which will function in:
- Sending Messages typed by the user,
- Receive messages from the bot
The ViewModel will also have a set of properties, which will be bound to the View for data representation or actions performed on the user interface.
Here is the initial code for the ViewModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | public class MainViewModel : BindableBase { public RelayCommand SendCommand { get; private set; } ObservableCollection<BotMessage> _botMessages; public ObservableCollection<BotMessage> BotMessages { get => _botMessages; set => SetProperty(ref _botMessages, value); } BotConnectorService _botService; private string _currentMessage; public string CurrentMessage { get => _currentMessage; set => SetProperty(ref _currentMessage, value); } public MainViewModel() { _botService = new BotConnectorService(); SendCommand = new RelayCommand(async () => await SendMessage(), () => string.IsNullOrEmpty(CurrentMessage)); BotMessages = new ObservableCollection<BotMessage>(); _botService.BotMessageReceived += OnBotMessageReceived; } /// <summary> /// Called when the bot sends a message. /// </summary> /// <param name="msgs"></param> private void OnBotMessageReceived(List<BotMessage> msgs) { foreach (var msg in msgs) { Device.BeginInvokeOnMainThread(() => { BotMessages.Add(msg); }); } } /// <summary> /// Send Messages to the Bot using the BotConnectorService Class /// </summary> /// <returns></returns> private async Task SendMessage() { try { await _botService.SetUpAsync(); var msg = new BotMessage() { Content = CurrentMessage, ISent = true, Time = DateTime.Now }; BotMessages.Add(msg); await _botService.SendMessageAsync(CurrentMessage); CurrentMessage = string.Empty; } catch (Exception e) { await App.Current.MainPage.DisplayAlert("Alert", "Problem connecting. Please check your internet connection.", "OK"); } } } |
The View:
We will build the view with Xaml. Firstly, remember we are using the MVVM design pattern and we should bind the view to the ViewModel, this is done by assigning a MainViewModel object to the view’s BIndingContext.
- Go to your MainPage.xaml and add this line of code: BindingContext = new MainViewModel();
- Inside the Views folder (Or where you have your views in your project), Create a folder named DataTemplates. This will contain the ViewCells which will serve as data templates for the messages sent by the user or the bot since their message bubbles should be distinct.
Let’s Create the data templates:
- Create a ViewCell, this will be used to create the message bubble for messages sent by the user.
- This bubble will be situated on the right and have a light sky blue color.
- The Message bubble will be rectangular, and fit the messages it contains.
- The Data template will be data bound to an instance of the BotMessage model
Here is the code for this :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="BotOnXamarin.Forms.Views.DataTemplates.SentByUser"> <ViewCell.View> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="60"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackLayout Margin="5, 5, 10, 5" Grid.Column="2" Grid.ColumnSpan="2" HorizontalOptions="FillAndExpand"> <Frame BackgroundColor="LightSkyBlue" OutlineColor="Transparent"> <Label Text="{Binding Content}" FontSize="Medium"/> </Frame> <Label FontSize="Micro" HorizontalOptions="End" TextColor="Gray" Text="{Binding Time, StringFormat='{0:MM/dd/yyyy hh:mm tt}'}"/> </StackLayout> </Grid> </ViewCell.View> </ViewCell> |
- Create another ViewCell, this will be used to create the message bubble for messages sent by the user.
- This bubble will be situated on the left and have a dodger blue color
- The Message bubble will be rectangular, and fit the messages it contains too.
- The Data template will be data bound to an instance of the BotMessage model too.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="BotOnXamarin.Forms.Views.DataTemplates.SentByBot"> <ViewCell.View> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="60"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackLayout Margin="10, 5, 5, 5" Grid.Column="0" Grid.ColumnSpan="2" HorizontalOptions="FillAndExpand"> <Frame BackgroundColor="DodgerBlue" OutlineColor="Transparent"> <Label Text="{Binding Content}" FontSize="Medium"/> </Frame> <Label FontSize="Micro" HorizontalOptions="Start" TextColor="Gray" Text="{Binding Time, StringFormat='{0:MM/dd/yyyy hh:mm tt}'}"/> </StackLayout> </Grid> </ViewCell.View> </ViewCell> |
In the Views folder, create a class this class is the Data template selector which will automatically select the data template for the messages sent by the user or the bot depending on the ISent Property of the BotMessage class.
This is its implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class ChatDataTemplateSelector : DataTemplateSelector { DataTemplate MessageFromUserTemplate; DataTemplate MessageFromBotTemplate; public ChatDataTemplateSelector() { MessageFromUserTemplate = new DataTemplate(typeof(SentByUser)); MessageFromBotTemplate = new DataTemplate(typeof(SentByBot)); } protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { var msg = item as BotMessage; DataTemplate template = null; if (msg == null) return null; if(msg.ISent == true) { template = MessageFromUserTemplate; } else { template = MessageFromBotTemplate; } return template; } } |
The Main page;
The main page will have a basic conversational interface, will a text entry, and a send button. The upper part of the page will contain a list view which will have the data template selector which we made earlier.
- The list will be bound to the View Model’s BotMessages
- The text box’s content will be bound to CurrentMessage property of the View Model
- The button will have its command bound to the SendCommand
Here is the code for this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:BotOnXamarin.Forms.Views" x:Class="BotOnXamarin.Forms.Views.MainPage"> <ContentPage.Resources> <ResourceDictionary> <local:ChatDataTemplateSelector x:Key="ChatDataTemplateSelector"/> </ResourceDictionary> </ContentPage.Resources> <ContentPage.Content> <StackLayout Margin="5"> <ListView x:Name="ChatListView" VerticalOptions="FillAndExpand" SelectedItem="{Binding SelectedMessage}" ItemsSource="{Binding BotMessages, Mode=TwoWay}" BackgroundColor="Azure" HasUnevenRows="True" SeparatorVisibility="None" ItemTemplate="{StaticResource ChatDataTemplateSelector}"/> <StackLayout Orientation="Horizontal"> <Entry Placeholder="Message" Margin="5" Keyboard="Chat" Text="{Binding CurrentMessage, Mode=TwoWay}" HorizontalOptions="FillAndExpand"/> <Button Text="Send" Command="{Binding SendCommand}"/> </StackLayout> </StackLayout> </ContentPage.Content> </ContentPage> |
The overall application should be up and running smoothly by now, and you should be able to communicate with your bot.
Now you have a client to communicate with your bot directly on your mobile device or your PC, Android, ios, Windows… depending on the platform you target with Xamarin.Forms. When running the sample you should take note that replies from the bot may take a few seconds before arriving it is not always instantaneous.
As I said earlier, this tutorial does not show how to handle messages with rich cards or attachments such as pictures… this will guide you in doing so, because living from this step to the next, is not difficult.
[zc4wp_za2]
If you liked this post, or it was useful to you, please ? like it, share it on twitter, facebook or other social media… in case you want to get updated on any new useful post, follow me on twitter and like my page on facebook.
Want to build an intelligent android application with Natural Language Processing features ? Check this post.
Follow me on social media and stay updated