« News from the MBS Xoj… | Home | MBS @ FMTraining.TV -… »

Using Bonjour to find iOS companion app

Let's say you have a Xojo desktop app and you like to have an iOS app, which can connect to the desktop application. We like to avoid the user entering some kind of connection information and just automatically connect. Then once connected, the iOS app can do some things and talk to the desktop app for more. Let's say the desktop app manages the database and the iOS app can show data on the go, e.g. after scanning a barcode.

For this project we use Xojo with two projects, one for desktop and one for iOS.

Desktop Project

We use a ServerSocket to wait for incoming connections. We let the ServerSocket pick a random port instead of relaying on a fixed port number.

Public Sub StartServer()
  ServerSocket = New MyServerSocket
  ServerSocket.MinimumSocketsAvailable = 3
  ServerSocket.Listen
  
  System.DebugLog "Waiting on port "+ServerSocket.Port.ToString
End Sub

We need a subclass of NSNetServiceMBS to advertise the service in the local network. We pick a name for our service, which may be the name of the user or his machine. For now it is "MyTestService". Next we decide how we name the service type. This needs to be unique, but e.g. "_appname._tcp." may do it with the app name inserted without space and in lowercase letters.

Public Sub AdvertiseService()
  Const domain = "" // default
  Const type = "_test._tcp."
  Const name = "MyTestService"
  
  Dim port As Integer = ServerSocket.port
  
  Service = New MyNetService(domain, type, name, port)
  Service.publish
  
End Sub

We need a subclass of TCP Socket to process incoming requests. Since a socket provides a stream of bytes which arrive in chunks, we have to decide how we send pieces of information. It is a big data stream and you need a delimited to see where data packets end. For our example we decide to use JSON with a newline character. All our data packets end with "}"+EndOfLine.Unix, so we can cut them there.

Here is the code for sending:

Public Sub Send(j as JSONItem)
  Me.Write j.ToString
  Me.Write EndOfLine.UNIX
  
  // we send EndOfLine, so we can watch on other side for } + endofline as delimiter
End Sub

And in DataAvailable event, we read data into a buffer and split as data arrives and call the Process method to work on the packets:

Sub DataAvailable()
  System.DebugLog CurrentMethodName
  
  Dim buf As String = Me.ReadAll
  
  buffer = buffer + buf
  
  Dim Delimiter As String = "}"+EndOfLine.UNIX
  Dim pos As Integer = buffer.IndexOf(Delimiter)
  
  While pos >= 0 
    
    Dim packet As String = buffer.Left(pos+1)
    
    Process packet
    
    buffer = buffer.Middle(pos+2)
    
    pos = buffer.IndexOf(Delimiter)
  Wend
  
End Sub

Don't worry with the delimiter. If your text contains a } and a new line character, the JSON will have it encoded as \r or \n in the JSON. But please let us know if you find an edge case.

iOS Project

Our subclass of NSNetServiceBrowserMBS class to look for service. We browse for "_test._tcp." in the example, but please change it to match whatever you use in the desktop application.

Public Sub StartBrowser()
  browser = New ServiceBrowser
  
  Const domain = "" // default
  Const type = "_test._tcp."
  
  browser.MainScreen = Self
  browser.searchForServicesOfType type, domain  
End Sub

Once you find a service via the browser, you need to resolve it with the NSNetServiceMBS class to know the IP and port.

Sub DidFindService(service as NSNetServiceMBS, moreComing as Boolean) Handles DidFindService
  System.DebugLog CurrentMethodName
  
  // make a new object so we have our events installed
  dim m as new NetworkService(Service)
  
  // need to resolve to know IP and Port
  
  m.resolve
  m.MainScreen = Me.MainScreen
  services.Add m

End Sub

We need a subclass of NSNetServiceMBS to resolve the services. When the browser finds a service, we resolve it to see the port number and connect via socket:

Public Sub FoundService(m as NetworkService)
  If socket = Nil Then // for now ignore second one found
    
    FoundService = m
    
    socket = New MySocket
    socket.Address = m.hostName
    Socket.Port = m.port
    Socket.MainScreen = Me
    
    System.DebugLog "Connect to "+m.hostName+":"+m.port.ToString
    StatusLabel.Text = "Connecting..."
    
    Socket.Connect
    
  End If
  
End Sub

And our Socket subclass can send/receive messages. We use JSON and the code in desktop and iOS is the same.

The example will be included in next pre-release. Let us know if you have questions.

20 05 23 - 10:08