Component Development: Building a multi-platform component for the Pexels API

A couple of days ago I announced the Pexels API component for Delphi. As Delphi Berlin makes it so easy to develop for multiple platforms, I wanted the component to be platform-independent – meaning: the component is supposed to work with all the different compilers that Delphi comes with.

Initially, the component was developed for VCL. The VCL is an option for Windows 32-Bit and 64-Bit only. FireMonkey needs to be supported as well. Yes, I do realized a FireMonkey version would suffice, but I consider VCL still my first choice for Windows-only applications. If another platform is needed later, a good application architecture will make it easy to tend to FireMonkey later as well.

I evaluated two approaches to offer my component as a VCL as well as a FireMonkey component:

  1. Use the same code and use compiler directives to support VCL and FMX at the same time
  2. Use an object-oriented approach using a common base class that has specialized implementations for the two frameworks.

To be honest, I never thought of option 2 before I realized that option 1 will never yield a good result. The approach to use compiler directives for different frameworks is simply wrong. FMX does support Win32 and Win64 as well. Thus, compiler directives will not be very helpful as compiler directives point to the compiler being used and not the framework. Furthermore, the code will be hard to maintain.

Considering option 1, I asked myself: Why not use object-oriented programming?

To start, I first analyzed which parts of the component are framework-specific. The component uses Web components to interact with the API, stores the information in a dataset and offers methods to retrieve the image(s).

The last part is the only part of the component that is not framework indifferent. VCL components offer properties of the class TPicture  for images. TPicture  is very specific and wraps bitmaps, icons and graphics (and any other type of image class you register). FireMonkey works very much differently as any bitmap image is wrapped using the class TBitmap . That very class offers pretty much the functionality of TPicture , but so much more. My last blog post dealt with TPicture  and how to decide which image format to use. TBitmap  is very much able to determine file formats from stream data. So comfortable.

This train of thought leads to the following base class definition:

type
  TFlxPexelsBase = class(TComponent)
  private
    // Fields...
    procedure SetDataset(const Value: TClientDataset);

  protected
    { Protected declarations }
    FDataset: TClientDataset;

    function GetFieldnameForSize( ASize : TPexelsImageSizes ): String;
    procedure RequestUrl(AUrl: String; AStream : TStream);

  public
    { Public declarations }
    constructor Create( AOwner: TComponent ); override;
    destructor Destroy; override;

    procedure Fetch;

    procedure GetCurrentPicture; overload; virtual;
    procedure GetPictureForId( AId: String ); overload; virtual;
    procedure GetPictureForId( AId: String; 
    	ASize: TPexelsImageSizes ); overload; virtual;
    
    procedure GetCurrentPicture( ASize: TPexelsImageSizes ); overload; virtual; abstract;

  published
    { Published declarations }

    property DefaultSize: TPexelsImageSizes read FDefaultSize write FDefaultSize;
    property MaxResults: Integer read FMaxResults write FMaxResults;

    property Keyword: String read FKeyword write FKeyword;
    property ApiKey: String read FApiKey write FApiKey;

    property Dataset : TClientDataset read FDataset write SetDataset;

  end;

Note that we do not have a property that can be used to access the image that has been retrieved. Furthermore, we need to provide the method to retrieve the picture for both frameworks. This leads to an abstract definition in the base class.

As I showed some of the VCL component the last time, I will now show a bit from the FireMonkey version. The definition is pretty straight forward:

type
  TFlxFmxPexels = class( TFlxPexelsBase )
  private
    FPicture: TBitmap;

    procedure SetPicture(const Value: TBitmap);
  public
    constructor Create( AOwner : TComponent ); override;
    destructor Destroy; override;

    procedure GetCurrentPicture(ASize: TPexelsImageSizes); override;

  published
    property Picture : TBitmap read  FPicture write SetPicture;

  end;

As you can see the type of Picture  is TBitmap  … something that would not fly in a VCL app.

The method TFlxFmxPexels.GetCurrentPicture  is not longer abstract and we will provide the following implementation in analogy to the VCL method I provided in the last blog post:

procedure TFlxFmxPexels.GetCurrentPicture(ASize: TPexelsImageSizes);
var
  lResponse : TMemoryStream;
  lUrl : String;

begin
  if Assigned( FDataset ) then
  begin
    if FDataset.Active then
    begin
      lUrl := FDataset.FieldByName(
        self.GetFieldnameForSize(ASize) ).AsString;

      lResponse := TMemoryStream.Create;
      try
        self.RequestUrl( lUrl, lResponse );
        lResponse.Position := 0;
        FPicture.LoadFromStream( lResponse );
      finally
        lResponse.Free;
      end;
    end;
  end;
end;

As you can see the loading of the image is so much easier in FireMonkey. One can really feel that the VCL was designed in another time where there were bitmaps only (the same applies to the Win32 API) when it came to images.

The last step is to register two components instead of one. Furthermore, the VCL component editors can be used with FireMonkey without any modification.

procedure Register;
begin
  RegisterComponents('Flixments VCL', [TFlxVclPexels]);
  RegisterComponentEditor(TFlxVclPexels, 
     TFlxPexelsComponentEditor);

  RegisterComponents('Flixments FMX', [TFlxFmxPexels]);
  RegisterComponentEditor(TFlxFmxPexels, 
     TFlxPexelsComponentEditor);
end;

Done.

To summarize, it can be truly said that learning good object-oriented design is much more important than asking for more features for multi-platform development to be added to the compilers. The class structure that I presented in this very post is very easy to maintain and can be understood within minutes. Features can be added on both frameworks and the components can develop in truly different directions. Aspects that can be done in both frameworks can be added to the base class and will immediately be available in both frameworks.

To make the point even clearer: With this component we have the means to make use of the Pexels API in a VCL app and in a FireMonkey app. This makes it possible to use the API in Mac OS, iOS, Android and Windows apps. Considering that teams need to split the development effort between them, the component approach is very valuable as any team member can use the API now in any kind of app without ever spending a single thought how it works.

The next blog post will show the demo apps. I made use of TMS Software’s FNC component pack that offers data-aware grids in the current beta. The platform independent grids can now be attached to data adapters that provide the data. But more about that next time…

Tags: , , , , , ,

Partnerships




Top