Category
The Initial Experiment
After some discussions on Delphi-PRAXiS, I decided to try an experiment to see how much of a Delphi application could be written purely with AI. I used Claude Code, and since it was a couple of days before Christmas when I started, asked it to write a cross-platform app that displayed a Christmas tree with ornaments that would fall and break, thinking I would submit the finished app a day before Christmas.
The program includes several images: a Christmas tree background and, for each ornament, two images, one intact while hanging on the tree and one broken after it's crashed on the floor. There are also a few crash sound files I found online; a random one is chosen each time.
Well, I got the Windows version working fairly quickly with only a little back-and-forth with Claude and without touching a line of code. I created the Christmas tree background and the ornaments in Artspace.ai. After converting the crash sounds to the right format for each platform, I got busy testing on various devices. The finished app is on Github.
Lesson 1: Playing Sound Files on Windows
I wanted the crash sound to happen after the bulb started falling and play immediately when it hit the floor and switched to the "broken" image. But one thing I noticed right away when testing on Windows was a significant delay when the sound played. Claude had written it to use the TMediaPlayer component, which made sense to me as this is a cross-platform media player in Delphi, but nothing I tried enabled a smooth transition from falling, to crash sound, to crash image. The ornament would fall, then disappear just before it hit the floor while it loaded and played the crash sound, then switch to the crash image, a terribly slow transition.
After some research and looking at other sample applications, I realized that for Windows (and Windows only), I should not use the TMediaPlayer for this but instead call sndPlaySound from the WinAPI unit.
I told Claude about this and it agreed and happily changed the code for me. Switching to this was a vast improvement; I'd probably need to implement threading and some intricate timing to get it perfect but this was supposed to be just a simple experiment of vibe coding and the result was close enough to be satisfying.
Later testing revealed the TMediaPlayer component worked fine for the other platforms.
Lesson 2: Device File Locations
Once the application concept was proven and working on the default Windows platform, I turned to the first mobile platform. Since I use an Android phone, it's most handy so started up wireless debugging, connected via adb, and deployed the project. I immediately got an error telling me that it found "unsealed contents present in the bundle root". After some research I figured out that unsigned files were in a location they shouldn't be; basically, I had not put the external files in the right folder.
For Windows programming, we've had the "luxury" (and sometimes plague) of being able to put and access files wherever we want. I had simply let Claude keep the sound and image files in the root of the project instead of moving them to a folder (after all, this was just a simple program; a larger, "real-life" application would be organized with layers of hierarchical folders). But mobile apps are much more restrictive; there are certain paths where external files are allowed and expected. On Android, these files are typically accessed from the application's assets\internal folder, which is returned as the "Documents" folder in Delphi code (more on this below).
Before continuing the story with mobile platforms, let me digress to the other "desktop" type platform. Deploying to a Mac desktop is second easiest (after Windows) because it doesn't have some of the limitations or screen size constraints that hand-held devices do. After hooking up to the Platform Assistant Server on my old Intel-based Mac and trying a build, I got the same "unsealed contents present in the bundle root" error I had when I first attempted to deploy to my Android phone.
The source of the problem was similar in both instances but required a different deployment strategy. Both Windows and Mac OSX can access files from the "application path" but the Mac compresses supporting files into a delivered bundle and when extracted, a "virtual" application path is created in Contents\MacOS. After adding files to the project's Deployment Manager interface (Project > Deployment), I had the Mac OSX version working as well.
Remember how the assets\internal path on an Android is returned as the standard "Documents" path? I wanted something similar for the iOS platform to keep the code consistent. Using Delphi's Deployment Manager, I had a great deal of difficulty getting the files into the right location, which should be Startup\Documents on iOS. Finally, I found and used the Grijjy Deploy Manager to rewrite the .dproj file and it worked beautifully! (Why setting that same path in Delphi's Deploy Manager wouldn't work is a frustrating mystery.)
To summarize, here are the destination folders for supplemental files on each platform:
- Windows: Application Path
- Mac OSX: Application Path (Virtualized)
- Android:
assets\internal - iOS:
Startup\Documents
On Windows, there's a Build Event to copy the files out to the output folder. For the other three platforms, the files must be listed in the Project Deployment window. As mentioned above, the Grijjy Deploy Manager can set up those files for Android and iOS, but you have to add the ones for the Mac OSX desktop yourself using the Delphi interface.
The code to reference these folders on the various platforms is simple, thanks to platform-agnostic functions that provide simple access to the platform-specific folders for each platform; these functions are in the System.IOUtils unit. Thus, the function Claude wrote (and I modified) that returns the correct path for each platform is simply this:
function TMainForm.GetResourcePath: string;
begin
{$IF DEFINED(ANDROID) OR DEFINED(IOS)}
// Android: resources in assets/internal folder
// iOS: resources in app's documents
Result := TPath.GetDocumentsPath;
{$ELSEIF DEFINED(MSWINDOWS) OR DEFINED(OSX)}
// Windows: resources in same directory as executable
// Mac OSX: resources in Content\MacOS "virtual" app path
Result := TPath.GetAppPath;
{$ELSE}
{$MESSAGE FATAL 'Unsupported platform'}
{$ENDIF}
end;
The key statements are where it assigns the Result: GetAppPath on Windows and Mac OSX, and GetDocumentsPath for Android and iOS, basically the Application path for desktop applications and the Documents path for mobile devices.
I could have used GetDocumentsPath for all platforms; on Mac OSX, the Deployment settings would've been a little different, for Windows, those files would be mixed with hundreds of other files in my general "Documents" folder, not ideal.
Lesson 3: New Apple Signing Requirements
Admittedly, I don't write apps for the Apple platforms very often; I write mostly for Windows and sometimes my Android phone. In the past, to generate the proper signing ID to prepare an iOS app, I could create a plain "Hello World" app in XCode on the Mac (using their default template without any code), give it a unique name, then use that in my Delphi project to code-sign the files. That no longer works, I needed to renew my Apple Developer account to properly sign and build even a simple app.
After paying the fee, waiting until the account was refreshed (the next day), and creating an identifier for the app, I was finally able to get the app working on my iPad.
Summary
There were several other small lessons along the way in the course of coding, conversing with AI, and general configuration and testing of the various platforms. There were also several delays due to the holidays, family get-togethers, and whatnot; so, of course, I didn't get this out before Christmas. But it's not quite New Year's yet and many people (who participate in this particular Christmas decoration) still have their Christmas tree up, so I hope it's still somewhat relevant in that respect.
Originally, this was going to take just a few hours and I was going to leave a paragraphical comment about the experience on the forum. I'm still impressed by what Claude Code accomplished but it turned into much more of a learning opportunity while I had some time off work.
The main reason for this blog, then, is to document some of the tripping points for my future self. I need to write some other cross-platform apps and I want to keep my skills fresh. I also realized that I had not played sound files before, or dealt with external files before other than a single database file in the standard "Documents" folder when I wrote some sample apps for a book a few years ago.
I hope this helps someone else, too. And if there's something here which I've written that could've been done differently or if there's an assumption I made that isn't quite right, please leave me a comment. I'm always open to learning a better way to do things!

Add new comment