❮ See all articles

Using SignalR to create real-time mobile app

How to use SignalR .NET library to create real-time applications.

Regular REST API is not enough to create a chat or a multiplayer game. In order to do that, we need to be notified everytime the server receives new data. SignalR comes to the rescue!

Let's imagine an app helping friends locate themselves. Every minute the app collects current user location and sends it to server, which resends this location to other connected users.

In this blog post we will show you how to setup SingalR on both server-side and mobile app.

What is SignalR?

SignalR is a .NET library to create real-time applications. Opposite to regular HTTP communication, SignalR allows pushing messages from server-side to connected clients without any previous request. Thanks to bi-directional communication the mobile app does not have to ask server to be up to date with the server content.

MagicGif

SignalR frees developer of selecting transport type, by selecting the best available transport given by client and server side. Websockets are not supported by server? No problem, let's talk with Long Pooling, or Server Sent Events. SignalR provides API that makes selected transport transparent.

Setup SignalR server

All SignalR server-side code is contained in a Hub which contains methods that can be called similarly to MVC Controllers. To be able to use those fully one need to register it in Startup.Configure as follows:

app.UseSignalR(route =>
{
    route.MapHub<SomeFancyNameHub>("/FancyHub");
});

Due to inheritance from SignalR Hub SomeFancyNameHub: Hub you already have access to Context, Clients and Groups properties.

 public abstract class Hub : IDisposable
    {
        protected Hub();

        public IHubCallerClients Clients { get; set; }
        public HubCallerContext Context { get; set; }
        public IGroupManager Groups { get; set; }

        public void Dispose();
        public virtual Task OnConnectedAsync();
        public virtual Task OnDisconnectedAsync(Exception exception);
        protected virtual void Dispose(bool disposing);
    }

This means for example that by overriding OnConnectedAsync() method for each connected client you can get it's connectionId from Context.ConnectionId, put it in one of the group from Groups using Groups.AddToGroupAsync() and notify all other Clients that a new one just joined the club Hub! From now till the hub is disposed server can access all those connections and notify clients whenever there is a need. The only thing that limits you is your

ImaginationGif

In our case, there is a mobile app that diplays team member location on a map. The teams are defined earlier so backend knows how many members there are, which ones are active and while they are, what is their location. Hub starts when first team member joins and based on his team name he is already grouped with.

Groups.AddToGroupAsync(Context.ConnectionId, usersTeamName);

From now on all team members joining will be assigned to this group. And when they do, those methods would be invoked:

await Clients.Caller.SendAsync("TeamJoined", JsonConvert.SerializeObject(teammates));

await Clients.OthersInGroup(usersTeamName).SendAsync("TeammateJoined", JsonConvert.SerializeObject(teammember));

where TeamMates is a collection of TeamMembers and TeamJoined along with TeammateJoined are messages/methods our iOS client listenes to.

So when a new team member joins the group, his name, profile picture and location is sent to other team members. This gives us the possibility to draw his avatar on a map in precise location so his teammates could know where he currently is. At the same time the team member gets the same info about his mates to draw on his map.

To keep hub alive and to stop it from being disposed each team member sends its location once in a while. This updated location is then being sent to other teammates so the map could refresh.

Thanks to SignalR hub we can have as many Groups as we like and keep the communication flow within that group (or team in our case).

Setup SignalR iOS client

To setup iOS client we will use mooozyk/SignalR-Client-Swift github repository.

Let's begin with initialization of Hub instance. In order to do that use HubConnectionBuilder class.

let hubConnection = HubConnectionBuilder(url: URL(string: "https://angrynerds.pl/stream")!).build()

HubConnectionBuilder allows us to customize connection options, by setting custom HTTP headers, or append authorization token.

let hubConnection = HubConnectionBuilder(url: URL(string: "https://angrynerds.pl/stream")!)
    .withHttpConnectionOptions { options in
        options.headers = ["foo": "bar"]
        options.accessTokenProvider = {
            return userProvider.token
        }
}.build()

After invoking build() method we are ready to begin converstion with server. To start connection call start() on HubConnection object.

SignalR API provides 2 basic methods to communicate - one to send the message, and the other to listen to any.

In our example we will notify server about current user location. To do it, we need to call invoke(method: String, arguments: [Any?], invocationDidComplete: (Error?) -> Void)

  • method: String - method server is listening to
  • arguments: [Any?] - array of objects we want to send
  • invocationDidComplete: (Error?) -> Void) - completion handler after completed invocation
hub.invoke(method: "updateLocation", arguments: [email, latitude, longitude], invocationDidComplete: nil)

To be notified on each player location update we will use hub.on(method: String, callback: ([Any?], TypeConverter) -> Void)

  • method: String - method server is listening to
  • callback: ([Any?], TypeConverter) -> Void) - callback method with 2 parameters, arguments and converter object, which helps us decode arguments to desired type

We are expecting json data as the only parameter in server message. JSON should contain list of connected users with their current location.

hub.on(method: method.rawValue, callback: { (args, typeConverter) in    
    guard let jsonString = try? typeConverter.convertFromWireType(obj: args[0], targetType: String.self),
      let data = jsonString?.data(using: .utf8),
      let users = try? JSONDecoder().decode([User].self, for: data)
      else { return }
    self.updateUI(with: users)

If we need to be up to date with connection status we can implement HubConnectionDelegate methods.

func connectionDidOpen(hubConnection: HubConnection!)
func connectionDidFailToOpen(error: Error)
func connectionDidClose(error: Error?)

Want to know more about the magic you can do with SignalR? Let us know in the comments if there's a specific topic you'd like us to cover!


Programming SignalR .NET

comments powered by Disqus
Let's get in touch!

Contact us today to receive a free quote for your app or project.