Regular REST API is not enough to create a chat or a multiplayer game. In order to do that, we need to be notified every time the server receives new data. SignalR comes to the rescue!
Let's imagine an app helping friends locate themselves. Every minute the app collects the current user location and sends it to the 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 the server to be up to date with the server content.
SignalR frees developer of selecting transport type, by selecting the best available transport given by client and server side. Websockets
are not supported by the server? No problem, let's talk with Long Pooling
, or Server Sent Events
. SignalR provides an 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
In our case, there is a mobile app that displays 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 TeamMember
s 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 a 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 conversation with the 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 the server about the current user location. To do it, we need to call invoke(method: String, arguments: [Any?], invocationDidComplete: (Error?) -> Void)
method: String
- method server is listening toarguments: [Any?]
- array of objects we want to sendinvocationDidComplete: (Error?) -> Void)
- completion handler after completed invocation
Like this:
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 tocallback: ([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 the server message. JSON should contain a 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 if there's a specific topic you'd like us to cover!
About the Authors
Łukasz Lech is an iOS developer at Angry Nerds - and one of the main creators of our iconic Cats and Dogs app! He’s a Swift enthusiast and he likes to explore this language’s potential as a backend technology. Łukasz is always up to date with all things Apple and he’s also interested in UX/UI design for mobile apps.
Jakub Tomalski is one of our most experienced .NET developers. As he says himself, he is fascinated by the power of API, so backend was the only career path he could choose! He specializes in backend development for mobile apps, but he has also hands-on experience working with web solutions.