Getting started with deepstreamHub is easy and takes less than ten minutes. However, if you have any questions, please get in touch.
This guide will take you through deepstreamHub's three core concepts: Records, Events and RPCs.
We'll use just the iOS client SDK in this tutorial.
Prerequisites
Before you begin, you'll need a few things set up:
- Xcode 8.0 or later
- An Xcode project targeting iOS 8.3 or above
- CocoaPods 1.0.0 or later
Add the deepstreamHub SDK to your iOS project
If you are setting up a new project, you need to install the SDK.
We recommend using CocoaPods to install the libraries. You can install Cocoapods by following the installation instructions.
To integrate the deepstreamHub SDK into one of your own projects, you will need to initialise a Podfile
and then install the pods for the libraries that you want to use. To do this:
-
If you don't have an Xcode project yet, create one now.
-
Create a
Podfile
if you don't have one:
$ cd your-project directory
$ pod init
- Add the pod to your Podfile:
pod 'DeepstreamIO'
A full example can be seen here.
- Install the pods and open the
.xcworkspace
file to see the project in Xcode.
$ pod install
$ open your-project.xcworkspace
NOTE: From now on, you'll need to use the .xcworkspace
file when opening your project.
- Add a bridging header to your project to let your Swift code use an Objective-C library.
You can create a bridging header by choosing File > New > File > (iOS, watchOS, tvOS, or macOS) > Source > Header File.
- In your bridging header file, we import the DeepstreamIO Objective-C header for the library we want to expose to Swift. For example:
#ifndef YourProject_Bridging_Header_h
#define YourProject_Bridging_Header_h
#import <DeepstreamIO/DeepstreamIO.h>
#endif /* YourProject_Bridging_Header_h */
Now, in Build Settings, in Swift Compiler - Code Generation, make sure the Objective-C Bridging Header build setting has a path to the bridging header file.
The path should be relative to your project, similar to the way your Info.plist
path is specified in Build Settings. e.g. ${PROJECT_DIR}/YourProject/YourProject-Bridging-Header.h
Adding Swift extensions
To make working with deepstreamHub as seamless as possible, there are a few Swift extensions that accompany the Cocoapod. These help bridge between Swift and the deepstreamHub SDK. The rest of the guide will assume you've carried out these steps:
- In Xcode, in the Project Navigator on the left, expand the Pods project and then Pods > DeepstreamIO
- Drag the directory Resources to the your project and ensure Create groups is selected and your project's target is too.
Create a free account and get your API key
Connect to deepstreamHub and log in
In the project's AppDelegate.swift
or perhaps in the func viewDidLoad()
method of your ViewController
.
Get your app url from the dashboard and establish a connection to deepstreamHub
guard let client = DeepstreamClient("<YOUR APP URL>") else {
// failed to connect to setup a DeepstreamClient
return
}
and log in (we didn't configure any authentication, so there are no credentials required)
guard let loginResult = client.login() else {
// failed to login
return
}
if (loginResult.getErrorEvent() == nil) {
print("Successfully logged in")
}
Records (realtime datastore)
Records are the documents in deepstreamHub’s realtime datastore. A record is identified by a unique id and can contain any kind of JSON data. Clients and backend processes can create, read, write, update and observe the entire record as well as paths within it. Any change is immediately synchronized amongst all connected subscribers.
Records can be arranged in lists and collections and can contain references to other records to allow for the modelling of relational data structures.
You can learn more about records in the records tutorial.
guard let record = self.client?.record.getRecord("test/johndoe") else {
// failed to get record handler
return
}
Values can be stored using the .set()
method
record.set(["firstname" : "John", "lastname": "Doe"].jsonElement)
In a UI based application, our ViewController will look like this:
class ViewController: UIViewController {
// Properties
private var client : DeepstreamClient?
private var record : Record?
override func viewDidLoad() {
super.viewDidLoad()
// Get the pre-configured client
guard let client = DeepstreamClient("<YOUR APP URL>") else {
// failed to connect to setup a DeepstreamClient
return
}
self.client = client
guard let loginResult = client.login() else {
// failed to login
return
}
if (loginResult.getErrorEvent() == nil) {
print("Successfully logged in")
}
// Create or retrieve a record with the name test/johndoe
// Get record handler
guard let record = self.client?.record.getRecord("test/johndoe") else {
// failed to get record handler
return
}
// Make a reference for later
self.record = record
self.record.set(["firstname" : "John", "lastname": "Doe"].jsonElement)
}
}
Now that we have initialised our DeepstreamClient and we've established a record handler, which we've then set with some values, let's set up two-way bindings with a UITextField.
Whenever a path within our record, e.g. firstname
changes we want to update the UITextField
. Whenever a user types, we want to update the record.
Add a UITextField to our ViewController
in the Storyboard
and with a reference to it in our ViewController
class (e.g. ni the ViewController.swift
file).
You'll need to drag the outlet from the Storyboard to your
ViewController` class to hook this up.
// ViewController properties
@IBOutlet weak var firstnameTextField: UITextField!
We can subscribe to changes for a record path by creating a callback that will handle such. We are going to create a generic RecordPathChangedCallback
that we can pass a UITextField
to and it will update it with.
// Create a generic RecordPathChangedCallback that will handle changes to a record path
final class NameRecordPathChangedCallback : NSObject, RecordPathChangedCallback {
var textField : UITextField!
init(textField: UITextField) {
self.textField = textField
super.init()
}
func onRecordPathChanged(_ recordName: String!, path: String!, data: JsonElement!) {
print("Record '\(recordName!)' changed, data is now: \(data)")
// Update text field in main thread
DispatchQueue.main.async {
self.textField.text = "\(data.getAsString()!)"
}
}
}
With this RecordPathChangedCallback
, we can subscribe to changes for the firstname
record.subscribe("firstname", recordPathChangedCallback: NameRecordPathChangedCallback(textField: self.firstnameTextField))
If we want to update the record path when the user types, we can setup a target on the UITextField
that listens when the user types like this in our viewDidLoad()
method.
self.firstnameTextField.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged)
Later in our ViewController
class we can add the action to handle this.
func editingChanged(_ sender: UITextField) {
if let record = self.record {
record.set("firstname", value: sender.text)
}
}
Events (publish-subscribe)
Events are deepstreamHub’s publish-subscribe mechanism. Clients and backend processes can subscribe to event-names (sometimes also called “topics” or “channels”) and receive messages published by other endpoints.
Events are non-persistent, one-off messages. For persistent data, please use records.
Clients and backend processes can receive events using .subscribe()
.
Like before, we can create a custom implementation of EventListener
that takes a UITextView
or perhaps UITextField
and appends new events. We can then subscribe to a given event name (e.g. test-event
) and pass this custom EventListener
.
You'll need to drag an outlet from the UI object in the Storyboard
to your ViewController
class to hook the UITextView
to the class.
@IBOutlet weak var subscribeTextView: UITextView!
final class DSEventListener : NSObject, EventListener {
private var textView : UITextView!
init(textView: UITextView) {
self.textView = textView
super.init()
}
func onEvent(_ eventName: String!, args: Any!) {
guard let value = args as? String else {
print("Error: Unable to cast args as String")
return
}
print("Subscriber: Event '\(eventName!)' occurred with '\(value)'")
self.textView.text?.append("Received test-event with \(value)\n")
}
}
self.client?.event.subscribe("test-event", eventListener: DSEventListener(textView: self.subscribeTextView))
We can publish events with .emit()
.
Let's add a UIButton
, an publish UITextField
and an subscribe UITextView
to our ViewController
in the Storyboard
, which will we create references for.
Like before, you'll need to drag the outlet from the Storyboard
to your ViewController
class to hook these up.
@IBOutlet weak var publishButton: UIButton!
@IBOutlet weak var publishTextField: UITextField!
@IBOutlet weak var subscribeTextView: UITextView!
func viewDidLoad() {
// setup DeepstreamClient and subscribe to event
...
self.publishButton.addTarget(self, action: #selector(publishButtonPressed(_:)), for: .touchUpInside)
}
func publishButtonPressed(_ sender: Any) {
guard let text = publishTextField.text, text.characters.count > 0 else {
return
}
self.client?.event.emit("test-event", data: text)
}
RPCs (request-response)
Remote Procedure Calls are deepstreamHub’s request-response mechanism. Clients and backend processes can register as “providers” for a given RPC, identified by a unique name. Other endpoints can request said RPC.
deepstreamHub will route requests to the right provider, load-balance between multiple providers for the same RPC, and handle data-serialisation and transport.
You answer requests for methods using .provide()
First, we'll create an implementation of an RpcRequestedListener
so that we can pass in a custom method that will handle the response from an RPC. On each call to the passed in rpc name, this will be run.
// Convenience typealias to simplify the parameters in DSRpcRequestedListener - not required
typealias RpcRequestedListenerHandler = ((String, Any, RpcResponse) -> Void)
final class DSRpcRequestedListener : NSObject, RpcRequestedListener {
private var handler : RpcRequestedListenerHandler!
init(handler: @escaping RpcRequestedListenerHandler) {
self.handler = handler
}
func onRPCRequested(_ rpcName: String!, data: Any!, response: RpcResponse!) {
self.handler(rpcName, data, response)
}
}
We can provide a specific method to handle an rpc call for the name multiply-number
Then we can setup a Target-Action so when the button is tapped, an rpc call will be made.
Don't forget to connect your UIButton
and UITextField
's to the ViewController
class.
@IBOutlet weak var makeMultiplyButton: UIButton!
@IBOutlet weak var requestValueTextField: UITextField!
@IBOutlet weak var displayResponseTextField: UITextField!
@IBOutlet weak var multiplyNumberTextField: UITextField!
func viewDidLoad() {
// setup DeepstreamClient and provide rpc method
...
client.rpc.provide("multiply-number",
rpcRequestedListener: DSRpcRequestedListener { (rpcName, data, response) in
print("RPC Provider: Got an RPC request")
guard let value = (data as? Float) else {
print("Error: Unable to cast data to Float")
return
}
guard let multiplyValue = self.multiplyNumberTextField.text,
multiplyValue.characters.count > 0 else {
print("Error: No multiple number provided")
return
}
guard let multiplyValueFloat = Float(multiplyValue) else {
print("Error: Unable to convert multiple value to Float")
return
}
response.send(value * multiplyValueFloat)
})
self.publishButton.addTarget(self, action: #selector(publishButtonPressed(_:)), for: .touchUpInside)
}
Finally, we can make a request using .make()
in the button method we connected the Target-Action to.
makeMultiplyRequestButtonPressed(_ sender: Any) {
guard let value = self.requestValueTextField.text,
value.characters.count > 0 else {
print("Error: No multiply number to request")
return
}
guard let valueFloat = Float(value) else {
print("Error: Unable to convert multiple value to Float")
return
}
guard let rpcResponse = self.client?.rpc.make("multiply-number", data: valueFloat) else {
print("Error: RPC failed")
self.displayResponseTextField.text = "Error"
return
}
guard let data = rpcResponse.getData() else {
print("Error: Unable to parse RPC data")
return
}
self.displayResponseTextField.text = "\(data)"
}
Where to go next?
Now that you've set up a basic swift project have a look at more advanced examples or get acquainted with deepstreamHub's other concepts