“Write once, run anywhere” has always been on software developers’ wishlists. Once a program has been written, it should be executable on all devices (desktop, tablet, smartphone) and operating systems (Windows, macOS, Linux, Android, iOS). Java comes quite close to this goal. The applications run in a virtual machine, so they can be executed on a wide variety of target devices. Here, you are usually limited to just one device class, such as the desktop. Mobile devices are too different to be covered reasonably from this source code base. Another disadvantage is the use of virtual machines. There is an intermediate layer between program code and system software, which makes hardware interactions difficult or even impossible. Performance can also be a problem for time-critical applications.
Another possibility for cross-platform applications is a web application. Web applications run in the browser and can be executed on a variety of heterogeneous devices, as long as the user interface is designed responsively. However, despite the extended possibilities of HTML5, access from the browser to the device’s hardware is severely limited. Similarly, offline use can only be realized with restrictions.
STAY TUNED!
Learn more about API Conference
Another argument for finding alternatives for cross-platform programming is the amount of development effort involved. If an application has to be developed separately for more than one system, effort increases almost linearly in relation to the number of systems and device types that need support. In this context, designing the user interface is particularly complex and demanding. Windows, macOS, the various window managers of Linux distributions in the desktop area, as well as Android and iOS in the mobile area, all require very different approaches in order to create sophisticated, customized user interfaces. This applies to both content and technical aspects. Hardly any developer or development team can be sufficiently familiar with every platform and its necessary tools and procedures.
Cross-platform interfaces with FireMonkey
Where do the difficulties with different graphical user interfaces on different systems come from? There are several reasons for this. First, each operating system provides a different application programming interface (API), and so the technical requirements differ. There are also differences in content. The appearance (design, layout, available controls/widgets) is different on each system. One example is the arrangement of navigation tabs.
There are also different requirements on the hardware side. Supported device screen size and resolutions are often very different. From a program development point of view, using a universal framework with the aim of abstracting hardware and system software is desirable. Such a framework for a user interface offers a uniform programming interface. You no longer have to worry about the concrete implementation of the requirements on the target system; in this case, the framework takes care of that.
There are several frameworks that pursue precisely this goal of system abstraction for graphical user interfaces, such as OpenGL. OpenGL is a specification in the form of a cross-platform, cross-language programming interface for the development of 2D and 3D graphics applications. The OpenGL standard includes about 250 commands that allow the real-time display of complex 3D scenes. The OpenGL API is implemented by system libraries, on some operating systems also as part of the graphics card drivers. OpenGL and variants of this system library are primarily used on the Linux and Android operating systems. The Metal 2 graphics engine is also supported for macOS. In contrast, DirectX is used for graphically demanding applications with Windows. The libraries for DirectX are also part of the operating system and close to the hardware (graphics card). DirectX and OpenGL enable graphics-intensive applications like games. However, their programming is anything but simple.
In order to create cross-device and cross-platform applications, another abstraction layer is necessary, such as the FireMonkey graphics framework. It communicates with the respective system libraries (OpenGL, DirectX) and standardizes the programming interface. This way, a universal programming interface is created that can be used across device and platform boundaries. FireMonkey enables the development of graphical applications for Windows, macOS, some Linux distributions, Android, and iOS. This covers all major device types and operating systems for user applications. FireMonkey offers the following functions [1]: Basic features such as windows, menus, and dialog boxes; the ability to display 2D and 3D graphics; lossless scaling and correct display on different devices and screens thanks to the use of a vector engine; display of color transitions; basic shapes for 2D graphics with an integrated set of brushes; pens, geometries, and transformations; basic objects and functions for rendering in 3D; read and write support for jpeg, png, tiff, gif file formats; animations calculated in the background thread with minimal CPU usage and automatic frame rate correction; and flexible layouts for relative positioning of interface elements. One special feature is the Designer, which allows the interface to be created entirely in one visual process. This integrates seamlessly with the Delphi, C++ Builder, and RAD Studio development environments.
System configuration at a glance
To create cross-device applications with FireMonkey as a universal graphics framework, you can use the RAD Studio [2] integrated development environment. It contains the products Delphi and C++ Builder. Delphi is both the name of the development environment and the underlying programming language, which is an extension of Object Pascal. The current version of RAD Studio is 10.4.2 with Professional, Enterprise, and Architect editions. For non-commercial or limited commercial use, there is the Community Edition, which is currently available in the version Delphi or C++ Builder 10.3.3 and can be downloaded for free after registration. This version is also suitable for creating cross-device applications for desktop and mobile devices.
During installation, you will be asked to select the appropriate target platforms and install the necessary packages (Fig. 1).
Fig. 1: Selecting the target platform during installation.
You can still adjust the selection you have made later, but it’s recommended that you do so right away. A few remarks on the choice of system environment and configuration against the background of cross-device and cross-platform development are in order. The development environment must be installed on a current version of Windows 10. If you want to create apps for different operating systems and device types, you need access to
additional systems, hardware, and possibly emulators (Android) or Simulator (iOS). This means that desktop applications for Windows can be created and executed directly on the development computer under the Windows operating system. Desktop apps for macOS and mobile apps for iOS require access to a Mac via the network. Under macOS, the Xcode development environment and the PAServer (Platform Assistant Server) program must be installed on it. The PAServer is used to remotely allow the creation of app packages over the network in an automated manner from RAD Studio. On Mac, an iPhone can be paired via USB cable or wirelessly to test iOS apps. Alternatively, you can use iOS Simulator.
Let’s move on to mobile apps for Android. These can also be created directly under Windows. A smartphone or tablet connected to the Windows computer via a USB cable or WLAN is suitable for testing. Be careful when using an Android emulator, however, as you may encounter some performance problems. Background: For Android apps created with RAD Studio, certain hardware requirements must be met. Specifically, it requires an ARM Cortex-A series CPU, ARMv7 instructions, NEON technology, and GPU. NEON technology is used to accelerate multimedia applications, especially graphics. Almost all current devices fulfill these requirements. However, current Android emulators use the hardware-supported virtualization module HAXM for faster speed. This is based on an adapted x86 image instead of ARM technology as is typical for Android. Therefore, HAXM-based emulators cannot be used. An emulator that directly emulates the ARM command sequence works, but this is usually very slow. It is better to use a physical smartphone or tablet for Android.
For Linux, it looks similar to macOS. You need access to a Linux system via the network. Under Linux, you also need to install the PAServer. You can find it on the RAD Studio pages or in the subdirectory of the installation.
A comprehensive device and system configuration is an important part of the preparatory work for all types of cross-device and cross-platform programming. In particular, creating apps for macOS and iOS requires the use of Xcode, and that requires an Apple computer or notebook. If you use a Mac or notebook with an Intel processor, you can set up the entire infrastructure on one device. You will need to install Windows and RAD Studio inside a virtual machine (VM) and access Xcode and Simulator for iOS from it. A Linux distribution can be set up in another VM. This procedure does not work for the latest notebooks from Apple with an M1 processor (ARM technology), because regular Windows 10 cannot be run on ARM processors.
STAY TUNED!
Learn more about API Conference
Cross-device apps
You start a new cross-device app in RAD Studio using the CROSS-DEVICE APPLICATION project template from the FILE| NEW PROJECT menu. The development environment generates a corresponding project framework. The active form is displayed and you can design the user interface using the graphical designer. This should be familiar to many developers. Select the appropriate components from the palette, place them on the form, and adjust the properties accordingly (Fig. 2).
When selecting the corresponding components, you will get an indication that shows which target platform is available for a user interface control. For example, a button is available on all supported platforms. Make sure that you create a responsive design for the interface. Ensure that the components are not positioned outright on the interface, but are automatically arranged using layout containers (panels). This is the only way to achieve an error-free layout on different operating systems and screen sizes and resolutions. For example, an arrangement in a grid form is supported. A graphic designer generates the necessary source code for the user interface and manual reworking of the generated code is not provided. Of course, it is also possible to generate the user interface dynamically at runtime in Delphi code.
The palette with components is extensive and contains non-visual elements for common requirements, such as components that help us connect to a database or provide access to hardware. In order to support the device variety, a graphic designer provides different views for the user interface. The master view is used to match the basic design of a device class, for example, iOS or Windows. Specific views for the iPhone or iPad, desktop, or Surface Pro gives us an impression of the target device’s final look. A shared cross-device preview is also possible.
Programming language
Delphi, the programming language used here, is structured and easy to learn. It offers the usual features with data types, variables, constants, extensive possibilities of object-oriented programming, generics, and more. The source code organization takes place in the form of units. These program modules have the file extension *.pas (Listing 1).
Listing 1: Building a unit in Delphi
unit ;
//
interface
//
uses ;
implementation
//
uses
//
initialization
//
finalization
//
end.
Here are the basic instructions for setting up a unit:
- Each unit begins with the keyword unit. This is followed by the name of the unit. It corresponds to the file name.
- Further libraries are integrated via the keyword uses.
- Functions, procedures, types etc. of this unit are made available to other units via the interface section. With procedures and functions, only the head of the routine (name, parameter, and possibly return value) stands. The definition follows in the implementation section.
- The implementation section contains the definition (the algorithm’s source code) of the functions, procedures, etc. declared in the interface section.
- The initialization section is used to write program code to be executed when the app starts (optional section).
- The finalization section includes the program code to be executed at the end. For instance, objects can be deleted (memory freed) when a form is closed.
The Delphi programming language is documented online on the manufacturer’s site. The development environment supports programming with the usual features like source code completion, suggestions, direct error checking, and more.
How to develop Web APIs?
Explore the API Development Track
Faster through intelligent help
Many programming tasks are repetitive. For this, RAD Studio offers the standard actions approach. These can be selected via the Action Manager at design time and assigned to relevant objects such as a button. A typical example is taking a photo with a mobile device’s integrated camera. The functionality needed for this is stored in the standard action and is automatically bound by calling it without having to write your own source code. This feature is helpful with different approaches on Android and iOS. Another interesting feature of the Designer is LiveBindings. Its goal is to decouple the application layers as far as possible and to separate the data storage from the user interface. LiveBindings can be used to bind controls or certain properties of controls to data sources. It is also possible to bind a property of a control to the property of another control. The binding direction can be specified, and bidirectional binding is also possible. An active binding is used to automatically exchange data between objects without the developer having to write source code. The required code is created internally by the development environment. In addition, binding the controls to objects and coupling controls to each other is easy.
Extension via GetIt
Rapid Application Development is based on the consistent use of components for the user interface design (visual controls) and for the provision of typical functions (non-visual controls). We can flexibly extend the components provided in the development environment. They are provided by third-party vendors and can be installed for use in Delphi/ C++ builders. This is easy using the GetIt extension manager integrated in the development environment. Components can be searched for by functionality and license and can be installed directly with just a few clicks (Fig. 3).
Fig. 3: RAD Studio’s GetIt extension manager
Features for mobile systems
A mobile app’s requirements differ from desktop applications in a number of ways. A typical use case is the integration of geo-services: determining position data and displaying it on a map. A map is encapsulated in RAD Studio by the TMapView component. Internally, both platforms use the in-house map service (Android: Google Maps, iOS: Apple Maps). A map-based app can be created in just a few minutes. Equally typical of mobile apps is the use of backend and cloud services. RAD Studio provides several non-visual components under the Baas Client tab for simplified configuration. To make it easier to implement functions such as user management or data storage, there are prepared components. The components under the SYSTEM tab go in the same direction. Here you will find components for Bluetooth and Beacon (Fig. 4), i.e. localization at close range based on Bluetooth Low Energy. There are also components for the motion sensor (TMotionSensor) and the orientation sensor (TOrientationSensor).
Fig. 4: Components for using beacons based on BLE.
At design time, drag and drop the appropriate components onto the application form. The development environment internally creates an object of the associated class. You can access the properties, procedures, and functions of the component (object) in the source code. Important settings can be made via configuration, reducing the programming effort.
Testing on target platforms
Cross-device and cross-platform applications must also be tested on different devices and systems during development. This is necessary because the design, device handling, and functionality can only be assessed on their target device. Since development with RAD Studio is done on Microsoft Windows, compiling, debugging, and testing for Windows applications is fast and efficient. Typically, the app is launched on the system in just a few moments. For other systems (macOS, iOS, Android, Linux), you have to activate the target platform in the project view (Fig. 5).
Fig. 5: Activating the target platform in the project view
For iOS, macOS, and Linux, the connection to the relevant system is established via the network, on the target computer via the PAServer (see above). This allows you to test the app on all target systems directly from the development environment. You can also debug the source code if necessary. Deploying all tasks required to create the corresponding binary programming packages from the Delphi source code and the start execution are triggered from the development environment.
Deployment for different systems
After (preliminary) completion of an app, it must be deployed to the target devices. The development environment also provides support for this with the Deployment Manager.
This tool gives you control over which files are copied to the target system. You can select additional files such as database drivers, and make them available for the target environment.
For the Windows operating system, you have two deployment options. The created app can be delivered as a classic desktop application. In this case, you must copy all the necessary files to the target system via a separate installer. You can also make the apps available via Microsoft’s Store. Originally, this was only intended for apps from the Universal Windows Platform. However, the Store is now open for classic Windows applications. In Delphi, you can create an app package for the store directly and submit it to Microsoft for the official release process.
For macOS, you can also create the application bundle from Delphi and deploy it via the App Store. To do this, you must meet the App Store conditions and have a paid developer account. Alternatively, on macOS, you can install and run an app without going through the Store.
Distribution for Linux is less standardized. You have to generate packages for distribution repositories yourself. The Deployment Manager supports you when creating the app packages for Android and iOS. For iOS, you will need a developer account with Apple. This is recommended during development, as it is a prerequisite for signing the app package. Only a signed package can be installed on an iPhone or iPad. Alternatively, you can work temporarily with free provisioning. Here, you only need an Apple ID. A developer account is not required [3]. However, there are several restrictions. For example, the test device and you can only run the installed app package for one week. Access to certain iOS functions is also restricted. To generate app packages for Android, it’s enough to install the Android SDK on the development computer under Windows.
Cross-device and cross-platform programming limitations
Without a doubt, cross-device and cross-platform programming has made great strides in recent years. Creating native apps for different operating systems and devices can be approached from a common source code base. However, there are some obstacles to consider:
- Specifics of the target platforms: Selected functions that are only available on a target system and a platform’s newest features are usually not programmable via a generic approach.
- The User Interface: Designing an app that looks good on both a desktop system and a smartphone is a challenge. You must find a compromise or provide adapted screens for different device types.
- Platform knowledge is necessary: Even if a large part of the platform-specific peculiarities are encapsulated by the components -having a basic understanding of programming on the target platform is a huge advantage. It will help you identify a cause and if needed, eliminate it if there’s an issue.
- Processor architecture: In addition to the device type (desktop, mobile) and the operating system, processor architecture also plays a role. With Apple’s switch from Intel-based to ARM-based processor architecture, programs that were originally written for Intel can only be executed using an emulation environment (Rosetta 2). This also applies to apps created with RAD Studio for macOS.
Overall, a cross-platform approach usually achieves the goal for standard tasks. But don’t forget that there are always situations where a feature does not work on one platform, even though it works flawlessly on others. The only thing that really helps here is extensive trial and error in order to select the right technical approach. If all critical functions work on all systems without any noticeable defects, you can proceed in a device- and system-neutral manner, saving yourself a lot of work.
STAY TUNED!
Learn more about API Conference
Conclusion
Quality cross-device and cross-platform apps are possible today. You can create apps for all target systems from one source code base, without them feeling like foreign objects. The approach presented here with RAD Studio and FireMonkey is characterized by the use of a graphic designer and development trimmed for efficiency. Native applications are created. This approach is well-suited for typical business applications with database connections, where a desktop application must be extended with mobile use cases.
Links & Literature
[1] http://www.firemonkeyx.com
[2] https://www.embarcadero.com/de/products/rad-studio/
[3] https://developer.apple.com/de/support/compare-memberships/