There seems to be a perception among some people that Delphi is in the unique position of allowing developers to share and re-use code across the various platforms that it’s compiler can now (and will soon) target. But this is not the case. Oxygene has had this capability right from the start.
To exercise this, at an admittedly rudimentary level so far, I created a simple application for iOS using Oxygene Nougat. The application is very simple, consisting of two edit fields and a button. You enter two numbers into the fields, press the button and the application pops up a message with the sum of those two numbers.
I then created the same application using Oxygene Cooper, for Android.
Clearly the amount of “business logic” involved is minimal. The vast majority of this simple application resides in the UI. But the object of this exercise was to explore the sharing of generic, non-platform-specific code between different Oxygene projects.
First, here is my Adder class that will be shared across the platforms:
namespace Deltics.Adder; interface type Adder = public class public property A: Integer; property B: Integer; property Sum: Integer read A + B; end; implementation end.
As I say, this is hardly rocket science. But something worth pointing out here is the efficiency of the Oxygene syntax.
First, to declare a read-write property, such as the A and B properties in this case, you do not need to erect the backing field and specify the read/write accessors. Simply declare the property and the type. You can of course specify read-only properties, and we have one here also which demonstrates another efficiency.
The Sum property is a read-only property which returns the result of the addition of the A and B properties. Such relationships can be expressed directly with read expressions. There is no need to implement a separate property read accessor method.
The other thing to note is that “units” in Oxygene are declared as members of a namespace. i.e. rather than namespaces being fabricated from arbitrarily unique unit names, they are a formal part of the language. There is no need for each unit to have a unique “unit name” – the filename distinguishes them as separate files after all.
Just for a fun comparison, here’s the equivalent Delphi class:
unit Deltics.Adder; interface type TAdder = class private fA: Integer; fB: Integer; function get_Sum: Integer; public property A: Integer read fA write fA; property B: Integer read fA write fA; property Sum: Integer read get_Sum; end; implementation function TAdder.get_Sum: Integer; begin result := fA + fB; end; end.
Just as Oxygene is a “better C#”, it’s also a “better Delphi”. imho. 🙂
But back to Oxygene.
I won’t bother you with the details of the applications themselves. Suffice to say that in each case I constructed the UI programmatically, as an exercise primarily in forcing myself to actually write some platform code as a learning exercise. But it should be noted that despite perceptions to the contrary in some quarters, Oxygene does have GUI building tools. Or rather, it works alongside the existing GUI building tools provided for each platform.
Did somebody say “native” ?
But I digress. Again. 🙂
In terms of solution structure (something that I expect to evolve as I gain experience with multiple platforms and Visual Studio solution concepts in general) I arrived at the following on disk:
Adder [Solution folder] Deltics.Adder.pas iAdder [iOS project folder] org.me.adder [Android project folder]
The Deltics.Adder unit (I think it is correct to still call it a unit even though the code is not part of a formal unit declaration as such) is added to each of the iAdder and org.me.adder projects using “Add Link” (i.e. reference) so that each project references this common file rather than having separate copies (which irritatingly is the default behaviour in Visual Studio when adding an existing file to a project).
So, now to those projects. Here is the code from the iOS GUI that employs my simple Adder class:
method RootViewController.btnSumPressed; var adder: Adder; msg: UIAlertView; begin adder := new Adder; adder.A := textA.text.integerValue; adder.B := textB.text.integerValue; // Initialise our Alert View Window with options msg := UIAlertView.alloc.initWithTitle('The Sum of the Parts is...') message(adder.Sum.stringValue) delegate(self) cancelButtonTitle('OK') otherButtonTitles(NIL); msg.show; end;
And here is the equivalent code from the Android GUI:
method MainActivity.ButtonOnClick(v: View); var myAdder: Adder; msg: AlertDialog; begin myAdder := new Adder; myAdder.A := Integer.parseInt(textA.Text.toString); myAdder.B := Integer.parseInt(textB.Text.toString); msg := new AlertDialog.Builder(self).create; msg.Title := 'The Sum of the Parts is...'; msg.Message := myAdder.Sum.toString; msg.show(); end;
Now I should mention that the AlertDialog code here is incomplete. As written above it currently presents a dialog with no buttons, meaning that I have to use the Android “back” hardware key to dismiss it, but the point of this post is the sharing of code between platforms not the platform UI concerns that I am still learning (I have to say that so far iOS has been the more pleasant experience, by far).
Of course this does raise the interesting point that on iOS there is no hardware “back button” which in turn raises the question, just how exactly does anyone think they can realistically create one app from one set of source that will behave properly on different platforms that themselves work differently ?
This is of course why I chose Oxygene. Yes, it is an inconvenience having to create platform specific GUI’s. But it is also in my view necessary, and to think otherwise is frankly delusional.
But back to the issue of the day.
Let us compare the usage of the shared “business object”. First, the setting of the A and B properties:
// Nougat - iOS adder.A := textA.text.integerValue; adder.B := textB.text.integerValue; // Cooper - Android myAdder.A := Integer.parseInt(textA.Text.toString); myAdder.B := Integer.parseInt(textB.Text.toString);
This illustrates some pretty fundamental platform differences between iOS and Android, each requiring different incantations to obtain an integer from the corresponding string representation. In the case of iOS/Cocoa, the string type itself has a method to achieve this – integerValue. In the case of Android/Java, we use the parseInt method of the Integer type.
Of perhaps more interest is what happens when we are working with the Sum property, to obtain the string representation of its integer value:
// Nougat - iOS message(adder.Sum.stringValue) // Cooper - Android msg.Message := myAdder.Sum.toString;
Here we something very strange. In each case, we are working with a string type property of a class which was not written specifically for any particular platform. Yet in each case the mechanism for obtaining the string representation of that integer is different and each appears to be a method of the string type itself.
How can the same string type have different methods ? And if it is the same type, why can we not use the same method in each case ?
The answer of course is that it is not the same type at all. Both Android and iOS have types which correspond to a “String” type, and Oxygene maps these onto our property type. It is a String but when required it is an NSString or a java.lang.String.
Although the mechanism involved is different, this effect is essentially the same as the approach in Delphi taken with the String type, where even with all of the additional RTTI that makes String so useful in Delphi, it is also a pointer to a simple null terminated string (PChar) that can be used directly (in most cases) with the platform API – i.e. Win32.
The Spirit of Delphi lives on. Just these days, not in Delphi itself.
Something else that is going on that is less obvious is that when compiled for Android, my Adder class extends the Java Object class. When compiled for iOS the same class extends the Cocoa NSObject.
Did someone say “native” ? 😉
At this point I should apologise to anyone at RemObjects if my understanding of what is going on is not accurate.
Some people will be aghast at the implications of this. This means after all that when you are writing the code for a business object that will be shared across different platforms you must be careful to avoid any platform specific incantations in that code. That could be difficult when even such things as simple as converting between string and integer representations are platform specific operations.
This is where Sugar comes in, and where the concept of “mapped types” in Oxygene comes to our aid.
That will be the feature in my next post on Oxygene.