In FireMonkey/XE2, most platform-specific functionality was filtered through a Platform singleton object. In this, the FMX.Platform unit defined an abstract TPlatform base class, with FMX.Platform.Win and FMX.Platform.Mac providing (private) concrete descendants of it (TPlatformWin and TPlatformMac respectively) that did the actual work. In FireMonkey/XE3, in contrast, the Platform singleton has been removed in favour of a TPlatformServices object that you query different interfaces from, each interface providing a different sort of ‘platform service’ – for example, there’s a clipboard service (IFMXClipboardService), menu service (IFMXMenuService), and so on.
In practice, TPlatformWin and TPlatformMac still exist, and at present, they also implement all the interfaces you can query for when it comes to their particular platform. Nonetheless, in principle, the new setup allows providing an alternative implementation for any given IFMXxxxService interface. For example, if you’ve had enough of the awful stock Mac menu bar code, you can write your own implementation of the IFMXMenuService interface, unregister the default implementation, then register your replacement:
type TMacMenuServiceFix = class(TInterfacedObject, IFMXMenuService) strict private FDefaultImpl: IFMXMenuService; procedure DestroyMenuItem(const AItem: IItemsContainer); //... public constructor Create; end; constructor TMacMenuServiceFix.Create; begin inherited Create; FDefaultImpl := IFMXMenuService( TPlatformServices.Current.GetPlatformService(IFMXMenuService)); TPlatformServices.Current.RemovePlatformService(IFMXMenuService); TPlatformServices.Current.AddPlatformService(IFMXMenuService, Self); end;
In this example I store a reference to the stock implementation in order to delegate to it for methods that I don’t wish to customise.
All in all I think this change a positive one. Nonetheless, the API is – how can I put this – rather ugly! Check out how I retrieve the default implementation of IFMXMenuService above:
FDefaultImpl := IFMXMenuService( TPlatformServices.Current.GetPlatformService(IFMXMenuService));
Notice how I have to type the word ‘service’ *four* times, the phrase ‘PlatformServices’ and ‘IFMXMenuService’ twice each, and the word ‘current’ once yet completely pointlessly – for some reason the person who wrote TPlatformServices doesn’t seem to realise there are things called class vars and class methods. I wonder if he’s related to whoever designed TTimeZone for the XE release…?
That said, wouldn’t it be better if we could just write this instead:
FDefaultImpl := TPlatformService.Get<IFMXMenuService>;
Similarly, in cases where we are open to the possibility there is no default implementation, you have to write code like the following, annoying cast included:
var MenuService: IFMXMenuService; begin if TPlatformServices.Current.SupportsPlatformService( IFMXMenuService, IInterface(MenuService) then
Wouldn’t that be better like this though?
var MenuService: IFMXMenuService; begin if TPlatformService.Available<IFMXMenuService>(MenuService) then
I think so, and whadya know, it took about two minutes to write:
TPlatformService = record class function Available<IntfType: IInterface>( out Service: IntfType): Boolean; static; class function Get<IntfType: IInterface>: IntfType; static; end; uses System.TypInfo; class function TPlatformService.Available<IntfType>( out Service: IntfType): Boolean; var Guid: TGUID; begin Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid; Result := TPlatformServices.Current.SupportsPlatformService( Guid, IInterface(Service)); end; class function TPlatformService.Get<IntfType>: IntfType; var Guid: TGUID; begin Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid; Result := IntfType(TPlatformServices.Current.GetPlatformService( Guid)); end;
[Edit: originally I had ‘TCurrentPlaformService’ rather than ‘TPlatformService’, but as Eric Grange rightly says in the comments, why have the word ‘current’ at all? There’s also an argument for dropping the ‘T’ prefix, but I’ve kept that in to be consistent with the wider D2009+ RTL.]