« MBS Xojo Plugins, ver… | Home | xDev Magazine 22.3 Is… »

Cross platform code in Xojo

At the training day before our Xojo conference, we showed how to make a little REST web API with a Xojo web project and provide data to desktop, console, iOS and Android applications. And all use very similar and almost identical code.

There are little differences like iOS has tables with sections and cell data objects while android uses value and detail texts for the rows. But we were impressed on how much is so similar. Let us show you the similarities and differences.

Open database

Let's start with opening a database in a web project. For the sample, we use the example database coming with the EddiesElectronics project, but you can use whatever SQLite database you like or connect to a database server.

In the web project, we add code in the Opening event:

EventHandler Sub Opening(args() As String) Var file As New FolderItem("/Users/cs/Training/EddiesElectronics.sqlite", folderitem.PathModes.Native) db = New SQLiteDatabase db.DatabaseFile = file db.Connect End EventHandler

Show Table in Web

First we like to show the Products table in the Web project by running a SQL select statement to give us the rows. We loop over the rows and query the field values to add them to the listbox.

EventHandler Sub Opening() update End EventHandler
Private Sub Update() Var db As SQLiteDatabase = app.db Var r As RowSet = db.SelectSQL("SELECT rowid,* FROM Products") While Not r.AfterLastRow Var ID As String = r.Column("rowid").StringValue Var Code As String = r.Column("Code").StringValue Var Name As String = r.Column("Name").StringValue Var Price As String = r.Column("Price").StringValue List.AddRow ID, Code, Name, Price r.MoveToNextRow Wend End Sub

Don't forget the MoveToNextRow, because otherwise you get an endless loop.

Provide web API

In the web project, we add a HandleURL event to the app class to process API calls. Currently we only use the path "api/product" in the URL to go to the branch of the code to handle product requests. If you later add more calls, you may do a dictionary to lookup the function to call or similar.

Similar to the loop above, but in each iteration we create a new JSONItem, put the values in from the fields and then append that to an array JSONItem. Once we got the rows, we return the JSON array as a string back to the caller using the write method and pass status code 200 for success. Don't forget to return true to notice Xojo, that you handled the request.

EventHandler Function HandleURL(request As WebRequest, response As WebResponse) As Boolean If request.Path = "api/product" Then Var j As New JSONItem Var db As SQLiteDatabase = app.db Var r As RowSet = db.SelectSQL("SELECT rowid,* FROM Products") While Not r.AfterLastRow Var p As New JSONItem Var ID As String = r.Column("rowid").StringValue Var Code As String = r.Column("Code").StringValue Var Name As String = r.Column("Name").StringValue Var Price As Double = r.Column("Price").DoubleValue p.Value("code") = Code p.Value("name") = Name p.Value("id") = ID p.Value("price") = price j.Append p r.MoveToNextRow Wend j.Compact = False response.Status = 200 // OK response.Write j.ToString Return True End If End EventHandler

Console App

Let us quickly make a console helper application to query the web service synchronously and show the data. The SendSync method sends the request wire the URL to the web API running in the web app on the same computer. We let the request take 60 seconds, so it should work even for a slow server on the other side of the globe. The result gets parsed as JSON and we can loop over the entries and show it:

EventHandler Function Run(args() as String) As Integer Var connection As New URLConnection Const URL = "http://127.0.0.1:8080/api/product" Const timeout = 60 Var data As String = connection.SendSync("GET", url, timeout) Var j As New JSONItem(data) Var u As Integer = j.Count-1 For i As Integer = 0 To u Var p As JSONItem = j.ChildAt(i) Var name As String = p.Value("name") Var code As String = p.Value("code") Var price As Double = p.Value("price") Print code+": "+name+" for "+Format(price, "0.00") Next End EventHandler

Alternative you can do a loop with DoEvents to have events run and use the asynchronous event based method to get the call. That is better if you run multiple requests in parallel.

Desktop App

In the desktop application project we send the request in the opening event and it runs asynchronously in the background.

EventHandler Sub Opening() Var URL As String = "http://127.0.0.1:8080/api/product" URLConnection1.Send "GET", URL End EventHandler

Later we get the content received event with the result. There we can again accept the content and parse it as JSON. We list the values into a DesktopListbox control.

EventHandler Sub ContentReceived(URL As String, HTTPStatus As Integer, content As String) If HTTPStatus = 200 Then Var j As New JSONItem(content) Var u As Integer = j.Count-1 For i As Integer = 0 To u Var p As JSONItem = j.ChildAt(i) Var ID As String = p.Value("id") Var name As String = p.Value("name") Var code As String = p.Value("code") Var price As Double = p.Value("price") list.AddRow id, code, name, Format(price, "0.00") Next Else Break // error? End If End EventHandler

As you may notice, we dropped an URLConnection on the window and just use it there. That is the easier mode in some cases. But in most cases you may use "new" keyword to make a new object at runtime. To catch the event you would connect with addHandler or subclass the URLConnection class.

iOS App

In the iOS project we use a iOSMobileTable on the screen. There in the Opening event we fire connection to query the data and the code looks exactly like the desktop application:

EventHandler Sub Opening() Var URL As String = "http://127.0.0.1:8080/api/product" URLConnection1.Send "GET", URL End EventHandler

When we get the ContentReceived, we do similar to the desktop project. We accept the result and parse the JSON. But since the iOSMobileTable control has sections to group items, we add at least an empty section title. Once we have the section, we can again loop over JSON and add the values to the table. But we need to create a cell object with the text to show and the detail text.

EventHandler Sub ContentReceived(URL As String, HTTPStatus As Integer, content As String) If HTTPStatus = 200 Then // we need at least one section table1.AddSection "" Var currentLocale As locale = locale.Current Var j As New JSONItem(content) Var u As Integer = j.Count-1 For i As Integer = 0 To u Var p As JSONItem = j.ChildAt(i) Var ID As String = p.Value("id") Var name As String = p.Value("name") Var code As String = p.Value("code") Var price As Double = p.Value("price") Var m As MobileTableCellData = table1.CreateCell(name, code + ": " + price.ToString(currentLocale, "0.00")) table1.AddRow 0, m Next Else Break End If End EventHandler

The code above uses ToString with locale parameter. We cache this locale value in a local variable above the loop to make the loop a little bit faster.

Android App

The Android project does the same as the iOS project, but a bit different. Let's start with the Opening event and you can look for the difference:

EventHandler Sub Opening() Var URL As String = "http://192.168.2.159:8080/api/product" URLConnection1.Send "GET", URL End EventHandler

Yes, it's the IP address. While iOS connects via localhost to the host Mac, on Android we are in a virtual machine and need to connect to the host computer over the local area network. My Mac got the 192.168.2.159 IP and thus I connect to the web app this way.

Once we get the ContentReceived event, we have similar code like in the iOS project, but without the section.

EventHandler Sub ContentReceived(URL As String, HTTPStatus As Integer, content As String) If HTTPStatus = 200 Then Var currentLocale As locale = locale.Current Var j As New JSONItem(content) Var u As Integer = j.Count-1 For i As Integer = 0 To u Var p As JSONItem = j.ChildAt(i) Var ID As String = p.Value("id") Var name As String = p.Value("name") Var code As String = p.Value("code") Var price As Double = p.Value("price") table1.AddRow name, code + ": " + price.ToString(currentLocale, "0.00") Next Else Break End If End EventHandler

As you see we cache locale again. But to add something to the AndroidMobileTable, we just call AddRow and pass the main text and the detail text as parameters. Someday we hope Xojo Inc. brings us an unified MobileTable control to do perfect cross platform code.

What's next?

If you like to make this a real project, please don't forget to change a few things:

You will need some kind of authentication for the web API. Maybe just check authorization header and let the user of the web api pass a kind of token or let them pass username and password.

The web app may show a login screen and let the admin review current records. It is quite handy to have an admin interface to see how many clients are connected or what records have been recently created.

We skip a lot of error checking to keep the examples small. All the URLConnections report errors via Error event and we ignore them above in the example. Also we should check if the result contains actual JSON and is the answer we expect. We just handle it as an array of records.

The database connection should be per session for the web users and per call for the web API calls, so you have proper error state per connection.

Let us know if you have questions and feel free to join a future training session.

03 05 24 - 10:07