Mobile improvements (with FPC): local notifications on Android, consent for AdMob, better iOS savegame location

Posted on

Local notifications example
Local notification on Android

Last news post was all about mobile development with Delphi. Let’s follow this with a list of improvements for mobile development with FPC!

  1. We have a new Android service local_notifications to send notifications.
    • An example showing the capabilities of it is in examples/mobile/local_notifications.

    • The API is nice and simple.

    • Big thanks to Hamid reza Kabiri for implementing this!

    • Note: These are local notifications, which means you send them when your application is running (to display immediately or after a specified delay). Contrast this with push notifications, which require a backend that determines the notification time — this would be a separate service.

    • Note: This service is for sending notifications using FPC. If you use Delphi, use FireMonkey API for notifications.

  2. Our AdMob service on Android now supports asking the user for consent before showing ads.

    • The service follows the Google-recommended pattern for asking for consent using User Messaging Platform. So the dialog looks consistent and non-surprising to users.

    • The change is backward compatible, so if you want to keep using it as before — you technically can. But note that asking for consent is legally required in Europe (European Economic Area and the UK).

    • This was again done by Hamid reza Kabiri. Big thanks!

  3. All Android services can automatically bump Android’s “min SDK version”. This allows you to “just use” services like admob or game_analytics, without additional tweaking of the manifest file.

  4. We have updated Gradle version used by our Android build files, to support Java >= 25 for Android building.

  5. On iOS, we change the castle-config:/ location to one recommended by Apple. Read on for details.

Change of iOS savegame location

Only for iOS applications built with FPC: We change where we store configuration files.

This applies to user preferences, savegames, etc. — everything you access using configuration files castle-config:/xxx URLs.

  • Previously, we stored config files (castle-config:/ URLs) in NSLibraryDirectory.
  • Now, we store them in NSApplicationSupportDirectory.

The reason for the change: the new location follows Apple recommendations. See Apple “File System Basics” -> “Where You Should Put Your App’s Files”:

“Put app-created support files in the Library/Application support/ directory. In general, this directory includes files that the app uses to run but that should remain hidden from the user. This directory can also include data files, configuration files, templates (…)”.

The change is aligned with what happens with Delphi. We determine iOS preferences location for Delphi using different code but we made sure it is effectively calculating the same. See Objective-C code doing the equivalent for FPC. So your iOS applications will read/write the same configuration, no matter which compiler you use.

The new directory is in practice a subdirectory of the old one: it is <sandbox>/Library/Application Support/, while previously it was <sandbox>/Library/.

Backward compatibility:

The change itself could be a trivial 1-line change, but we need to do more work to be backward compatible. If you have an existing application, using CGE and FPC, on iOS, and you have saved some configuration -> if you rebuild your application with the new engine, it will still open the old configuration.

The backward compatibility mechanism should “just work” if all you do is read / write config files using castle-config:/... URLs. Opening castle-config:/... URLs with engine API, testing with UriExists -> will just work.

To account for edge-cases, and people who write their own Objective-C code, let us describe the details of how the compatibility was done:

  • The library now exposes an additional routine, CGEApp_SetFallbackConfigDirectory.

  • Our Objective-C code passes there the old config location.

  • From your application, if you try to open the URL at castle-config:/, or check for existence using UriExists, we will first check the new config location, and then (if set) we will use the old location provided to CGEApp_SetFallbackConfigDirectory. This happens when ResolveCastleConfigUrl is called with 2nd parameter UseFallbackIfNotExists set to true.

  • Nothing else is using the fallback config directory. Writes will always go to the new, proper config directory. So if you open and save a savegame on iOS, created with the old application version, the new application version will put it in the new location. Further reads will ignore the old file in the old location, since the file will exist in the new location. This should be invisible to the user on iOS (<sandbox>/Library/ is not visible in any application), so it should not cause problems.

Possible known gaps:

  1. Using FindFiles with castle-config protocol, like FindFiles('castle-config:/savegames/*.sav', ...) will not see the old savegames on iOS (in a fallback directory). If you need this to work:
    1. Avoid FindFiles with castle-config protocol on iOS with FPC. Use UriFileExists instead, e.g. check a list of possible savegame filenames. To be clear: You only need to be careful about this if you have users that have savegames in the old location (made with an old engine version). This concern doesn’t apply to new applications made with CGE for iOS.

    2. If the above advice is difficult, a fallback is to hack engine tools/build-tool/data/ios/xcode_project/cge_project_name/OpenGLController.m to use NSLibraryDirectory again (instead of NSApplicationSupportDirectory) for the config location. This is brutal but simple and effective: it gets you back to the old state, and it’s just a 1-line change. And let us know — so we can design a proper upgrade plan!

  2. If you relied on UriToFilenameSafe to convert castle-config protocol on iOS with FPC, it will not see the fallback file. We don’t know in which context you use UriToFilenameSafe, so it only resolves to the new config location. If this is a problem, our advice:

    1. First of all, don’t use UriToFilenameSafe — operate on URLs as much as possible, provide URLs to all CGE routines.

    2. Use ResolveCastleConfigUrl with 2nd parameter UseFallbackIfNotExists set to true.

Please contact us if this is causing any problems for you — we will propose solutions 🙂

As always, we hope you have fun developing games using Castle Game Engine and we appreciate your donations.

Comments on the forum ➤

AnsiString vs Utf8String properly declared, Delphi: we no longer force AnsiString to UTF-8

Posted on

Running Castle Game Engine application on Android using Delphi

( This announcement is a bit non-exciting small improvement around the engine codebase. For something more exciting, that I was working on through September, see the screenshot attached here :), and stay tuned for more news soon. )

Previously, our engine did SetMultiByteConversionCodePage(CP_UTF8) forcing all AnsiStrings (with both FPC and Delphi) to have UTF-8 encoding. This is also consistent with what Lazarus LCL was (and still is) doing.

We have extended our possibilities now, and changed what happens with Delphi by default (with FPC, nothing changes by default):

  1. Throughout the engine, we use now always Utf8String when we want to say “8-bit string with UTF-8 encoding”. Aside from the practical gains (see below), this naturally just looks better: we explicitly express our intention.

    We use AnsiString in code only when (very seldom) we really mean “8-bit string with whatever encoding happens to be the platform default”.

    We have tested that with Delphi (unfortunately, not so with FPC) the proper implicit conversions are always done when you assign between AnsiString (even if they have non-UTF-8 content) and Utf8String. See lots of new automatic tests in

    Note: 90% code of the engine still uses just String (not Utf8String or AnsiString). That is deliberate and simple: we want to just use default string type, which means UnicodeString (String=UnicodeString with Delphi, or FPC Unicode RTL) or AnsiString (FPC, without FPC Unicode RTL). Nothing changes in this regard.

  2. We added defines to control whether the engine does SetMultiByteConversionCodePage(CP_UTF8), forcing all AnsiString to contain UTF-8. And by default, it is no longer done when compiled with Delphi.

    • CASTLE_ANSISTRING_FORCE_UTF8: This is the previous behavior. And it is still default with FPC.
    • CASTLE_ANSISTRING_UNCHANGED: This is the new possible behavior. It is (since now) the default with Delphi.

    Note that CASTLE_ANSISTRING_UNCHANGED is not supported with FPC, due to FPC (at least 3.2.2) not doing AnsiString vs Utf8String conversions automatically (but you can force it by defining also I_UNDERSTAND_THAT_NON_ASCII_CHARACTERS_ARE_BROKEN if you really need it, and understand the consequences).

    We changed Delphi default to CASTLE_ANSISTRING_UNCHANGED, because it works flawlessly with Delphi in our tests, and it seems safer — we don’t mess with global settings if we don’t need to. This is nice if you have an existing codebase that assumes AnsiString don’t hold UTF-8 encoded data. This is also important when our code is run as part of Delphi IDE, when you install our Delphi packages — this way we don’t mess assumptions that may be made by Delphi IDE or other 3rd-party Delphi packages you have.

  3. Along the way, we have also fixed and simplified some details around how Unicode characters are handled.

    • Reading JSON files (with our FpJson) with non-ASCII text now works on Delphi reliably. Our FpJson unit (and friend) use String or Utf8String, and don’t mess with encoding.

    • FPHashCompatibility unit, complicated compatibility hack for Delphi, is gone.

    • A few cases when we used UTF-8 with Delphi are fixed. E.g. with Delphi, one cannot do S := S + C when S is Utf8String and C is AnsiChar and C may be only a part of multi-byte character: Delphi will pad C to be a valid Unicode character. See TTestCompiler.TestAddingIncompleteUtf8 test in tests/code/testcases/testcompiler.pas for details.

    • CastleReadLink on Delphi/Linux fixed.

    • GetTempFileNamePrefix on Delphi/Windows fixed.

    • We cleaned some unused Vampyre Imaging files (like dglOpenGL copy used by Vampyre demos) to not confuse developers what is used by our engine.

For more details about how CASTLE_ANSISTRING_FORCE_UTF8 and CASTLE_ANSISTRING_UNCHANGED work see

If you like our work, please support us on Patreon!

Comments on the forum ➤