How to Build iOS Chat Apps using Xamarin and Twilio

May 03, 2016
Written by

ipm

Twilio IP Messaging for iOS is now supported on Xamarin! In just 10 minutes, this tutorial will show you how to build an iOS chat app using Xamarin and Twilio IP messaging.

Get Ready to Build

If you’re building this tutorial on a Mac, make sure you have Xamarin Studio with Xamarin.iOS installed. Windows developers will need to have:

  • Visual Studio 2015
  • Xamarin.iOS for Visual Studio installed and configured to build the iOS application (requires a Mac on the network)
  • Administrator access to your development machine

With those prerequisites installed, we’re almost ready to start building our app. Before we can do that we’ll need to get our Twilio account set up so that we can use IP Messaging in our app.

Getting Started

As you might expect the first thing we need is a Twilio account. Don’t worry, trial accounts are free so if you don’t already have one, sign up on the Twilio website. I’ll wait right here while you do it.

You’re back! Fantastic. Let’s move on to the second thing you’ll need.

To make IP Messaging work we need to create an Access Token. An access token tells Twilio who a chat user is and what they can and can’t do within the IP Messaging service.  Tokens are generated on the server in order to help protect your Twilio account secrets.

You can write a token generator in your programming language of choice, or to get chatting faster download and configure use one of our quickstarts.

If you are a .NET developer on Windows, refer to this guide and follow the instructions to get the ASP.NET version of our IP Messaging quickstart running. If you’re on a Mac or you just want to use a different backend language on Windows you can head here and choose a language of your choice from the list of available quickstarts.

Once you have a quickstart setup, open it in your browser.  You should find yourself looking at a chat application.  If you are this means that you’ve generated an access token on the server and been assigned a random username. It should look like this:

We’ll use this same backend to request a token for our mobile application so keep it running and keep the URL handy. I’ll refer to this URL later as YOUR_TOKEN_SERVER_URL.

There’s one more thing to mention before we move on and it has to do with how the token is accessed from the app. If you are on Windows and targeting iOS or if you are trying to run on device, the server running on localhost won’t be reachable on the public internet. You have two choices. One option is deploying the project to a web server (or to Azure, Heroku, etc.) and using that URL instead of localhost. Alternatively, you can use something like ngrok to create an HTTP tunnel to your localhost instance. You can follow this post to see how and why you might want to use ngrok. If you’re on Windows, make sure to run ngrok like this: ngrok http [your port] -http-header="localhost:[your port]

Let’s move on to our mobile app code.

Adding the Twilio IP Messaging SDK

Alright iOS ninja, are you ready to build a chat application? Fantastic!  First clone this starter project from Github and open it in your Xamarin IDE of choice.

The starter project has the main user interface objects for our chat app already created in Main.storyboard and some basic keyboard handling code in ViewController.cs. It also contains a table view cell that we’ll use to display chat messages. What we’ll do over the next few minutes is light up those UI objects with Twilio IP Messaging.

Begin by adding the Twilio IP Messaging NuGet packages to the project. Right-click on the Packages node inside the TwilioMiniHack.iOS project and select Add packages…:

Add packages

Search for “Twilio IP Messaging” in the Add Packages dialog, check the boxes next to “Twilio IP Messaging for Xamarin” and “Twilio Common Library for Xamarin” and click the Add Package button in the bottom-right:

Packages dialog

Now that we have the Twilio IP Messaging SDKs in our project let’s add the following using statements to the top of ViewController.cs:

using Twilio.Common;
using Twilio.IPMessaging;

In ViewController.cs, let’s configure the ViewController it so that it implements the following interfaces:

public partial class ViewController : UIViewController, IUITextFieldDelegate,ITwilioIPMessagingClientDelegate, ITwilioAccessManagerDelegate
{
    // ...
}

ITwilioIPMessagingClientDelegate handles the events we receive from the IP Messaging service while ITwilioAccessManagerDelegate is used to coordinate authentication events with the service. We’ll add the methods to implement these interfaces as we go along.

Creating a Message Data Source

Since this is a chat app and we’ll work with messages we will create a class that will help manage them for our table view. This class will be a subclass of UITableViewSource so that we can not only store our Message objects but also provide our table view with the methods it needs to render them.

Create a new class and call it MessagesDataSource. Replace the template code in this class with the following:

using System;
using System.Collections.Generic;
using Twilio.IPMessaging;
using UIKit;

namespace TwilioMiniHack.iOS
{
    class MessagesDataSource : UITableViewSource
    {
    	public List<Message> Messages { get; private set; } = new List<Message>();
    }
}

We’ll need the ability to add messages to the list as they come in so add that to MessagesDataSource:

public void AddMessage (Message msg)
{
    Messages.Add (msg);
}

Finally, we need the NumberOfSections, RowsInSection and GetCell method overrides that configure our table view to display the messages. Add these to MessagesDataSource:

public override nint NumberOfSections(UITableView tableView)
{
    return 1;
}

public override nint RowsInSection(UITableView tableView, nint section)
{
   return Messages.Count;
}

public override nint RowsInSection(UITableView tableView, nint section)
{
    return Messages.Count;
}

public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
{
    var message = Messages [indexPath.Row];

    var cell = tableView.DequeueReusableCell ("MessageCell") as MessageCell;
    cell.Message = message;
    cell.SetNeedsUpdateConstraints ();
    cell.UpdateConstraintsIfNeeded ();

    return cell;
}

The GetCell method uses the MessageCell class that is already provided in the solution.

The table view data source is now good to go so let’s populate our table view. Back in ViewDidLoad of the ViewController.cs, create an instance of MessagesDataSource and set it as the table source. While we’re here we’ll also provide the table view with some row dimension information:


MessagesDataSource dataSource;

public async override void ViewDidLoad ()
{
   base.ViewDidLoad();

   // Perform any additional setup after loading the view, typically from a nib.
   NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, KeyboardWillShow);
   NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidShowNotification, KeyboardDidShow);
   NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, KeyboardWillHide);

   this.View.AddGestureRecognizer(new UITapGestureRecognizer(() =>
   {
     this.messageTextField.ResignFirstResponder();
   }));

   dataSource = new MessagesDataSource ();
   tableView.Source = dataSource;
   tableView.RowHeight = UITableView.AutomaticDimension;
   tableView.EstimatedRowHeight = 70;
}

 

Our table view is now ready to go.  We just need to connect to Twilio IP Messaging and load it up.

Connecting to Twilio IP Messaging

Add some instance variables to our ViewController class to keep track of IP Messaging related objects:

// Our chat client
TwilioIPMessagingClient client;
// The channel we'll chat in
Channel generalChannel;
// Our username when we connect
string identity;

Now add a method that will fetch an access token from our server:

async Task<string> GetToken ()
{
   var deviceId = UIDevice.CurrentDevice.IdentifierForVendor.AsString ();
   // If you’re using the PHP server, your endpoint will be: $"https://YOUR_TOKEN_SERVER_URL/token.php?device={deviceId}";
   var tokenEndpoint = $"https://YOUR_TOKEN_SERVER_URL/token?device={deviceId}";
   var http = new HttpClient ();
   var data = await http.GetStringAsync (tokenEndpoint);

   var json = JsonObject.Parse (data);
   // Set the identity for use later, this is our username
   identity = json ["identity"]?.ToString ()?.Trim ('"');

   return json["token"]?.ToString ()?.Trim ('"');
}

 

We pass in the device ID as a unique identifier and a token is returned that includes our identity. Excellent, now let’s go back to ViewDidLoad and create the IP Messaging client using the token. Add the following code to ViewDidLoad:

var token = await GetToken ();
this.NavigationItem.Prompt = $"Logged in as {identity}";
var accessManager = TwilioAccessManager.Create (token, this);
client = TwilioIPMessagingClient.Create (accessManager, this);

We use the returned token to create an instance of TwilioAccessManager and then use that instance to create a TwilioIPMessagingClient. We set our view controller as the delegate so we can handle the various delegate methods the TwilioIPMessagingClient needs.

Now is also a great time to implement the ITwilioAccessManager interface. Add the following code to the ViewController class:

Foundation.Export("accessManagerTokenExpired:")]
public void TokenExpired(Twilio.Common.TwilioAccessManager accessManager)
{
    Console.WriteLine("token expired");
}

[Foundation.Export("accessManager:error:")]
public void Error(Twilio.Common.TwilioAccessManager accessManager

Now use the IP Messaging client to get a list of channels and either join the general channel if it already exists, or create the channel if it doesn’t exist. Add this code to the bottom of ViewDidLoad:

client.GetChannelsList ((result, channels) => {
   generalChannel = channels.GetChannelWithUniqueName ("general");
   if (generalChannel != null)
   {
       generalChannel.Join(r =>
       {
           Console.WriteLine("successfully joined general channel!");
       });
   }
   else
   {
       var options = new NSDictionary("TWMChannelOptionFriendlyName", "General Chat Channel", "TWMChannelOptionType", 0);

       channels.CreateChannel(options, (creationResult, channel) => {
           if (creationResult.IsSuccessful())
           {
               generalChannel = channel;
               generalChannel.Join(r => {
                   generalChannel.SetUniqueName("general", res => { });
               });
           }
       });
   }
});

 

Next go into Main.storyboard and double-click the SEND button in the bottom right to generate a click event for the button.

Use the following code for the button’s Touch Up Inside event:


partial void SendButton_TouchUpInside (UIButton sender)
{
   var msg = generalChannel.Messages.CreateMessage (messageTextField.Text);
   sendButton.Enabled = false;
   generalChannel.Messages.SendMessage(msg, r => {
       BeginInvokeOnMainThread (() => {
          messageTextField.Text = string.Empty;
          sendButton.Enabled = true;
       });

   });
}

 

This button will allow us to send messages but what about when we receive messages in the channel? Let’s add some code to handle that now. When a message is sent to the channel we’ll use a method that handles the ipMessagingClient:channel:messageAdded: delegate method to load it into the datasource for our Messages and reload the table view:

Foundation.Export("ipMessagingClient:channel:messageAdded:")]
public void MessageAdded(TwilioIPMessagingClient client

With this in place we can send and receive messages on the general channel and have a functioning chat app in iOS! For a quick test you can switch back to the browser app and send a message. It should show up in your iOS app. Now that you have it working feel free to explore the Twilio Docs to find out what else you can do with your application.

Wrap Up

In this post we used the new Xamarin support for Twilio IP Messaging to build a simple chat application in iOS. Now that you have a working chat app there’s a lot you could do to improve it. Try some of these ideas:

  • Add login and user identity management instead of using random usernames
  • Add the ability to create new channels
  • Add typing indication

Whatever it is you create next we’d love to see it. Let me know about it on Twitter @brentschooley or email me at brent@twilio.com.