And Then There Was API 2.0
By Thom McGrath on
Xojo 2019r2 has launched with a whole ton of changes and deprecations wrapped under the umbrella label "API 2.0." When I say that certain methods got replaced, I really mean the old version has been deprecated and a replacement introduced. Most projects will open and run in 2019r2 without any changes.
There is also no need to rewrite existing code using the new changes. It's an option, but not a requirement. As a beta tester and developer of a medium-sized open source project, I did choose to "solve" every single deprecation warning so I could best test the new API. It absolutely helped find bugs that I believe would not have been caught without spending the effort I did, but it is not something I would recommend without a compelling reason.
And now, since this release adds a lot of great stuff, let's get to it.
FolderItem on macOS no Longer Uses Deprecated APIs
Apple deprecated the FSRef APIs way back in macOS 10.8, but Xojo has been stalling to modernize
FolderItem. The first handful of macOS Catalina betas broke a number of things with FSRef which caused a bit of a panic in the Xojo community. Relying on these deprecated APIs is considered technical debt, and technical debt can strangle a project at unexpected times. In this case, Xojo had to rapidly get
FolderItem modernized, though that mostly involved just copying
Xojo.IO.FolderItem and making some adjustments. Luckily for everybody except Xojo, later Catalina betas fixed the issues and apps which were compiled with the older
FolderItem will continue to work.
FolderItem, which is not renamed, should work without any code changes. Though there are some additions and changes.
FolderItem.Directory is now
FolderItem.IsFolder, for example.
One perplexing thing about the updated
FolderItem is how it handles API 2.0's goal of using more exceptions: it doesn't. While that's good for legacy code, it doesn't make a whole ton of sense. I wouldn't be surprised if methods such as
CreateAsFolder get deprecated and replaced in a future version.
One important thing to note is
AbsolutePath is fully gone. Code relying on it will not compile. This property was deprecated by Xojo in 2013r1, and Classic Mac style absolute paths were deprecated by Apple much before that.
Easier Database Accessor Methods
Using prepared statements with SQLite in Xojo requires a lot of annoying boilerplate. So API 2.0 introduces more convenient replacements for
SelectSQL. Both will accept the command as a string, and also the parameter values at the same time, so the following is now valid syntax:
Var Results As RowSet = DB.SelectSQL("SELECT * FROM objects WHERE id = ?1;", 128)
This a fantastic addition, and works for the other database classes too. These new methods fire exceptions on error though, so be aware. I personally consider that a great thing, but it's a behavior to be aware of when making use of the new methods.
The bad news with these methods is they always create a new prepared statement. There is no caching going on, and worse, if called without parameter values the statement is still prepared. This ends up hurting performance pretty significantly. There is a Feedback case for the issue.
Zero-Based String Accessor Methods
The most frustrating thing about
Mid have been their use of one-based offsets. So if I wanted to get the position of everything after the equals sign in
"Key=Value", I would do
Var Source As String = "Key=Value" Var Position As Integer = InStr(Source, "=") Var Value As String = Mid(Source, Position + 1)
But if I wanted the key, I would have to do
Var Key As String = Left(Source, Position - 1)
So remembering the offsets has been obnoxious. The issue here is
Left is zero-based, because if I want everything from the beginning of the string, I tell
Left that I want to skip zero characters. But
InStr told me the first character was one, not zero. So if I just pipe the result of
Left, then I get the wrong result.
So the new methods
Middle have been introduced. They work exactly the same as their now-deprecated counterparts, except they are zero-based. So the previous example becomes
Var Source As String = "Key=Value" Var Position As Integer = Source.IndexOf("=") Var Value As String = Source.Middle(Position + 1) Var Key As String = Source.Left(Position)
And everything is nice and consistent. Notice that these are extension methods though? I'll explain that later.
Modern JSON Routines
Back in my JSON Performance Tests I demonstrated on the monumental performance difference between
Xojo.Data. Xojo's implementation is only bested by the MBS plugins, and even then, performance was pretty similar. But
Xojo.Data.GenerateJSON couldn't produce pretty JSON, which hurt performance dearly when needed. Plus, the methods require usage of the
Auto datatypes, which was a hassle given issues such as
Text being unacceptably slow on Windows.
So good news, we now have
Xojo.GenerateJSON(Value As Variant, Expanded As Boolean = False) and
Xojo.ParseJSON(Value As String) As Variant. That means we now have a built-in, high performance JSON solution using traditional datatypes, that can even pretty-print. I'm very happy with this addition.
DateTime, TimeZone, and DateInterval Bring a Mixed Bag of Sanity and Frustration
Date class has some problems that can be avoided if used correctly, but the mutability of the class (which, for the uninitiated, means its values can be edited) can create some subtle and hard-to-track errors. Plus there are other frustrations, like having to create a date object just to find out the current time zone.
Now thanks to some convenience methods, finding the current time zone is just a simple call to
DateTime.SecondsFrom1970 is always in GMT, which makes it great for storing the value in a database.
But I mentioned the mutability of
Date because these new classes are immutable. Since
DateTime cannot be modified, getting the current date in a specific time zone requires two dates:
Var Now As DateTime = DateTime.Now Var NowGMT As New DateTime(Now.SecondsFrom1970, New TimeZone("GMT"))
In my opinion, any time code requires creating a like-class just to immediately throw it away, that's a sign the API isn't robust enough.
Oh, and notice I used
Var Now As DateTime = DateTime.Now instead of
Var Now As New DateTime? Yeah... while
DateTime has constructors, they're only useful for getting other timestamps. If the current time is needed,
DateTime.Now must be used instead... for some reason.
DateTime should have a
Constructor(AtTimeZone As TimeZone = TimeZone.Current) signature, though I know it would need to be implemented as two different signatures behind the scenes. The point is, there's no good reason for
New DateTime not to work, and no good reason not to support asking for the current time in a different time zone. Feedback case has been filed for this one too.
DateTime's immutability extends to its properties too. It can't be changed, but it can be manipulated by adding or subtracting
DateInterval instances. For some reason, it isn't possible to subtract two
DateTime instances to produce a
So if I wanted a
DateTime one month in the future, I'd call
Var Now As DateTime = DateTime.Now Var Future As DateTime = Now + New DateInterval(0, 1)
Which is ok, but constructing the
DateInterval instances is a bit annoying. In this case, since the first parameter is years and the next parameter is months, the years parameter must be specified even though it's zero. The later parameters are implied zero. But if I wanted five minutes, the code would look like
Var Now As DateTime = DateTime.Now Var Future As DateTime = Now + New DateInterval(0, 0, 0, 0, 5)
And now we're getting a bit obnoxious. I would love to see string-based interval creation. This is supported by other languages such as PHP. Using their implementation as inspiration, a five minute interval could be specified as
Var Interval As New DateInterval("PT5M")
Or if I wanted one month and five minutes
Var Interval As New DateInterval("P1MT5M")
While the spec isn't exactly natural, it's not hard to get used to either. This would become really powerful if
Operator_SubtractRight with string. They would create the
DateInterval from the string, thus making the following code valid.
Var Now As DateTime = DateTime.Now Var Future As DateTime = Now + "PT5M"
The good news is all of these suggestions can be implemented without breaking any code. And if a developer wanted to make some subclasses, these ideas are all doable. At the time of this writing, I do not yet have feature requests for these DateInterval improvements.
The Global Namespace Is Now the Enemy
When talking about the new string accessors, I mentioned that the new methods were only extension methods. That's actually true of a large number of global methods. With the exception of the math methods such as
Abs, and so on, if it's in the global namespace, it's now deprecated. That means
MsgBox, and so on. Some have no simple replacement, such as
MidB. For those methods, when it's time to transition to API 2.0, the code needs to be rewritten to use
MemoryBlock instead. Others are simple on the surface, such as in the examples above. For many uses, it's not hard to transition to the extension methods.
However, extension methods are not as flexible as regular methods. Consider the following code:
// Yes, I know this is stupid code. That's besides the point. Var Key As String = "Key" Var Value As String = "Value" Var Position As Integer = InStr(Key + "=" + Value, "=")
An extension method is impossible here. There are two options. The first is to use an intermediate:
Var Combined As String = Key + "=" + Value Var Position As Integer = Combined.IndexOf("=")
Or something that feels really awful:
Var Position As Integer = CType(Key + "=" + Value, String).IndexOf("=")
Yeah... that'll function. There's not a good solution to this problem. At least not without some compiler work to make the following syntax functional:
Var Position As Integer = (Key + "=" + Value).IndexOf("=")
Speaking of Compiler Work…
Another handful of changes is property return types. For example,
Timer.Mode As Integer is now
Timer.RunMode As Timer.RunModes. The goal is to replace the Integer-based constants with Enumerations. This is a fine goal, but somewhat frustrating, and could have been avoided entirely with some compiler work.
If Enums could be freely converted to and from Integer without using
CType, then all Xojo would need to do is introduce the new Enumerations and change the property type. The old Integer constants would continue to work, and those could be deprecated instead of the property itself. We'd gain a feature, and have less code to adjust.
But Xojo is trying to make do without a compiler engineer, so this didn't happen.
Oh Right, Events Changed Too
Many events got renamed to be more clear. The convention should consistently be VerbNoun. For example,
TextChange has become
TextChanged. In general,
Action events have been renamed depending on the type of action that could have happened. For example,
PushButton.Action is now
PushButton.Pressed because they only way to trigger it is via a press… unless using use the
Push method, which I never recommend. But
CheckBox.Action is now
CheckBox.ValueChanged because the event will fire when pressed by the user or changed in code. In theory, these changes are perfectly fine.
In practice though, if a subclass uses any of the new event names as its own event name, it will not compile in 2019r2. The solution depends entirely on the needs of the class and the project itself. Projects that should maintain backwards compatibility will have a harder time resolving this issue. In some cases, the best option is to use an older IDE and refactor code to avoid the new event names.
But an easier solution is not using 2019r2. Time is money, so that is an option developers will need to consider if running into this problem.
And Here's Where Things Go Really off the Rails
Up until now, I've covered changes that I've felt were good for the language or at least worth suffering for. Here's where I talk about the stuff that got changed just for the sake of changing things.
To be fair, all of the changes have motivation behind them. And if creating a new language from scratch, I'd have far less of an issue with them. But Xojo isn't a new language, and there is 20 years worth of code to consider. Many of these changes do not provide enough benefit to be worth changing. Every language has its warts. PHP, for example, can't decide wether its string functions should have an underscore after their
str prefix. They are unfortunate, but we suffer them because the code isn't worth changing.
Let's start with the big one: Arrays. Instead of
Remove we have
RemoveRowAt. Much of the community is particularly objected to this one, because for anybody with any experience writing code, Arrays do not contains rows. Rows are for tables and databases. Arrays are more of an abstract concept.
I spoke to Geoff on this topic for a while. I'm not going to go into detail of the conversation because those conversations occur in a confidence that I do not want to betray. I believe he would not object to me sharing this though. His logic is that "append" is not a word we use in everyday English. He's right, nobody says "honey, append ketchup to the shopping list." Instead we say "add." But it seems simply
AddAt were not good enough. My issue is that arrays are also not a concept humans use. Ask the average person what an array is, and they'll respond either "I don't know" or "a group of satellites, right?" So why is append such a problem? It's literally the perfect verb.
MenuItem.Append got changed to
MenuItem.AddMenu which is an equally terrible name. Besides making a find-and-replace for
AddMenu implies that I want to add a submenu.
AddItem would have been much nicer, and would have worked just fine for arrays too!
Oh and we also got
Array.FirstRowIndex for um... reasons. I guess.
One of the naming rules is "no abbreviations" so things like
Graphics.FillRect have become
Graphics properties and methods got a rename.
FontName, as well as similar changes to the related font properties. A real head-scratcher is
DrawText. I don't understand that one, because
Text is the "bad new Xojo framework" data type that API 2.0 is designed to sweep under the rug. Since there's no functional change to the method, why did it get renamed? It wasn't a change made out of necessity, it's just a change whose noun doesn't match up with the datatype it acts on. Same for
Don't Dimension Me a New Variable
Attentive readers will notice that all my examples use
Var instead of
Dim. That's because API 2.0 also introduces
Var as an alternate syntax for declaring variables. The logic here is that
But wait… if that's the goal for
Dim, why rename
Array.Append to something LESS familiar to developers coming from other languages? For new users, which keyword is used makes no difference at all because it's a new concept anyway.
Norman Palardy had suggested it would have been just as easy to make
Dim optional instead. As in declaring a variable would be allowed to be
Key As String. This is the option I would have argued in favor of myself.
Let me be clear though,
Dim is not deprecated. Switching to
Var is completely optional. Right now at least.
Time to Play Armchair Quarterback
Being that I worked on the product for 5 years, it's hard for me not to provide my input. Some comments by a Xojo team member in the Xojo forum prerelease channel suggest that Xojo is not intending to modernize their own IDE with these changes. New code may or may not even use the new API depending on individual developer habits, and old code won't even be switched out when being worked on. That bugs me. A lot. While this is a good sign that there's no urgency for developers to update their own code, it means Xojo expects to maintain both APIs for... well... ever. Which begs the question... what was the point? The only thing that effectively achieves is the Xojo team has more on their plate to maintain. It also means Xojo isn't even eating their own dog food. I understand better than most that the 2019r2 IDE could not have used API 2. But to have no intention to migrate the IDE to the new API doesn't really sell changes to the community.
In fact, what API 2 does is give existing developers a reason to a) not upgrade/renew and b) maybe even look elsewhere. That's something Xojo really doesn't need right now.
What Xojo does need real soon is a compelling reason to upgrade. Some killer feature that makes something possible that is currently impossible. Generics come to mind, but they have no way to achieve that without a compiler engineer. I'm not sure exactly what that killer feature would be though. Maybe they're banking on Web 2.0 and/or Android. That might be a fair plan, given the lack of ability to improve the core language.
The point is, the event renames that have caused some projects to be impossible to compile in R2 are keeping certain developers in the past. It's not worth spending the effort to use R2. So a future update needs to add something that is worth spending the time on.
I want to make something perfectly clear: I want Xojo to succeed. I've spent considerable effort this beta cycle to help get these changes tested. I'd still be working with Xojo if I had the option. Although I'm critical of many of these changes, at the end of the day, they've happened. My Beacon project is all-in with API 2.0. I may not agree with
AddRow but I'm still embracing it. Some of the additions in 2019r2 are really good. Plus this review mentions nothing at all of the IDE, which has a much faster layouts editor.
There will be a lot of commotion in the Xojo community about API 2.0. I'm sure a lot of it will be in frustration. Give 2019r2 an honest go. API 2.0 changes aside, the release feels like one of those "stable" versions that developers can rely on. Don't count the release out immediately just because some questionable name changes were made.
But be critical too.