Quantcast
Channel: Delphi Worlds
Viewing all 80 articles
Browse latest View live

Making XE7 projects compile for iOS 8.0

$
0
0

Whilst updating this demo project, and compiling for the device (in my case iOS 8.0, as I have Xcode 6.0.1), I received an error as outlined in this post on the Embarcadero community website.

As per the response from Stefan D on October 4th, a solution has been posted, here. To save wading through that, I thought I’d post the changes required, here. These steps might also work for earlier versions of Delphi (e.g XE6 and XE5). The steps I carried out are:

  1. Copy Macapi.Dispatch.pas from the XE7 source/rtl/osx folder into another directory. In my case I have a folder called Patches/Mobile/XE7. I’m likely to add other files to this path later.
  2. Modify the code in the copied file thus:
    const
      // libdispatch = '/usr/lib/system/libdispatch.dylib';
      libdispatch = '/usr/lib/libSystem.dylib';
    
  3. Make sure the path to this file is in the Delphi Library options, by selecting Tools|Options, select Library under Environment Options/Delphi Options, and add the path to the Library Path.
  4. Remember to do a Build (instead of compile) when recompiling.

This has the demo project compiling for the device, however there is a warning regarding libReachability.a, so I suspect that may need addressing. I’ll know when I have a working  iOS 8.0 device to deploy to (my phone is currently not working).

The post Making XE7 projects compile for iOS 8.0 appeared first on Delphi Worlds.


Delphi… Out Of This World!

XE8.. bringing 64 bit goodness to iOS

$
0
0

RAD Studio XE8 has recently been released, and with it comes support for compiling your iOS apps for 64 bit devices. Now, if only I had one! Looks like I’ll be bugging my girlfriend to borrow her phone for testing on the device 😉

There’s also a stack other new features and bug fixes. Check it all out, here.

The post XE8.. bringing 64 bit goodness to iOS appeared first on Delphi Worlds.

Fixing a detail insert bug in FireDAC with LiveBindings

$
0
0

I’ve recently started a project in Delphi XE8 that has me using Delphi a lot more again. It’s designed to be cross-platform, however for now the GUI side uses VCL controls, so the initial iteration is going to be restricted to Windows. I’ll be re-using the back end however, so FireDAC was chosen for database connectivity. I also chose to use LiveBindings to ease the rest into “cross-platformess”, and so that I could bind the data to just about any controls I want.

So far the design is fairly simple: I have a few TListView components that are displaying some data; one of which is the detail in a classic master-detail arrangement. I’m inserting detail records in code in the back end by passing the ID of the master record, and the ID of another source being used as a lookup, viz:

function TdmMain.AddListItem(ListID, TitleID: Integer): Integer;
begin
  qryItems.Append;
  qryItemsListID.Value := ListID;
  qryItemsTitleID.Value := TitleID;
  qryItemsSeq.Value := 0; //
  qryItemsActive.Value := 1;
  qryItems.Post;
  Result := qryItemsID.Value;
end;

I figured this would be pretty straightforward, however when I called this method I’d receive the error:

Dataset not in edit or insert mode

On the line just after the append. I figured there must be some kind of side-effect that was causing the dataset to change from dsInsert state to dsBrowse. Debugging that kind of situation can be real messy, as the code traces deep into the VCL (I still need to figure out how to avoid tracing into the System unit).

I figured if it was due to notifications to observers that the dataset had changed, wrapping the call thus:

function TdmMain.AddListItem(ListID, TitleID: Integer): Integer;
begin
  qryItems.DisableControls;
  try
    qryItems.Append;
    qryItemsListID.Value := ListID;
    qryItemsTitleID.Value := TitleID;
    qryItemsSeq.Value := 0; //
    qryItemsActive.Value := 1;
    qryItems.Post;
  finally
    qryItems.EnableControls;
  end;
  Result := qryItemsID.Value;
end;

Would alleviate the immediate problem, which it does, however it introduces another: the list control isn’t updated with the new data.

The next step was to Google for whether anyone else is having the same problem. Nine times out of ten, this would provide me an answer. Either this was the other one, or I just wasn’t able to find the right search terms, so I posted a message to Embarcadero’s developer forums. I seriously wasn’t expecting an answer for a few days, however Marco Cantu answered very promptly, followed by Dmitry Arefiev, both Embarcadero employees, the latter being their FireDAC expert (unsurprising, since he is the original author).

Dmitry’s solution was right on the money. There’s an issue with the GetRecNo method of TFDDataset, in the FireDAC.Comp.Dataset unit. Here’s where it pays to have an edition of Delphi that has source:

In order to fix the issue, I made a copy of the unit (I prefer to leave the original source intact), put it somewhere in the project path, and made the following change:

function TFDDataset.GetRecNo: Integer;
begin
  if IsEmpty or (State = dsInsert) then
    Result := -1
  else
    Result := GetRowIndex() + FUnidirRecsPurged + 1;
end;

I also copied the FireDAC.inc unit into the same location, because the compiler needs to find that file in order to successfully compile.

Problem solved! I created this post in case someone else comes across this issue.

The post Fixing a detail insert bug in FireDAC with LiveBindings appeared first on Delphi Worlds.

Moving controls into view when the virtual keyboard is shown, revisited

$
0
0

Just over 2 years ago, I posted this article:

http://delphi.radsoft.com.au/2013/10/moving-controls-into-view-when-the-virtual-keyboard-is-shown/

Well, a little water has passed under the bridge since then, in terms of the changes to Delphi (I think I used XE5 for that article?), and in terms of my thinking. A recent task at work had me thinking there could be a more straightforward approach. This article, and the associated code goes a long way to solving that.

Delphi now has (not sure if it did before, and I’m not going to look), a means of responding to when the virtual keyboard is shown or hidden, without actually assigning a handler in a form (i.e. the OnVirtualKeyboardHidden and OnVirtualKeyboardShown events). I decided to create a manager class that would take care of pretty much everything; all that needs to be done is for the OnFocusChanged handler to be assigned to the manager.

The manager class is called TVKControlManager. To respond to the virtual keyboard being hidden/shown, it subscribes to the TVKStateChangeMessage. (See here for information about the message manager).

When the virtual keyboard is shown, the manager keeps a copy of the keyboard bounds, and calls PositionAdjust. PositionAdjust determines what the focused control is, and finds the closest parent that is either a scrollbox, or the furthest up the chain that is non-nil. If the position hasn’t already been adjusted, it saves the parents position, and the height of its parent (i.e. the next level up). It then adjusts the Y value of the “parents” position “upwards”, to just above the virtual keyboard bounds, and also makes sure the height of the parent’s parent is increased to allow for the shift.

When the virtual keyboard is hidden, the position is “restored” in PositionRestore, where everything is put back the way it was before the virtual keyboard was shown.

When the focus changes, (but the virtual keyboard remains visible), the PositionAdjust is called again in case there’s any adjustment that needs to be done.

I’ve also added in events in case the end-user wishes to do some “tweaking” after the position is adjusted or restored.

There’s a caveat in order for this to work properly: If you’re using a scrollbox on which you place your controls, the Align property cannot be Top, or Client (at least; these are the two that come to mind immediately). The reason for this is that attempting to set the Y value of the Position property will result in it being reset when FMX realigns the controls. One way out of this is to have Align set to Bottom, and explicitly set Position.Y to wherever it needs to be when the form is shown, or when the orientation changes. Orientation changes result in the virtual keyboard being hidden, so the old issue of controls being out of position when the virtual keyboard is visible and the orientation changes, has now disappeared.

Instead of including code snippets here, I’ve included the entire example project. I’ve tested it on iOS simulator, device (iPhone 6S) and an Android device (Nexus 5). Note: The “Previous” and “Next” buttons will not appear on iPad. For some reason or another, the FMX code does not allow the virtual keyboard toolbar on iPad; I may visit this issue in another post.

Feel free to make comments/suggestions.

The post Moving controls into view when the virtual keyboard is shown, revisited appeared first on Delphi Worlds.

Some stuff about Delphi 10 Seattle

$
0
0

This isn’t quite what I’d call a review; it’s more like: I’ve been using Delphi 10 Seattle for a while now, and this is what I’ve liked, or found interesting 🙂 I’ve been very slightly behind the changes that have been happening with Delphi, so forgive me if I mention something that was included in an earlier version.

The first thing that comes to mind is that it is able to use waaaaay more memory when compiling. This is very helpful for those who have a lot of units in their compile path; like where I currently work. It’s especially true when compiling for mobile.

Since I’ve been in cross-platform mode (both for work, and personally), being able to target any platform easily has been a boon. As part of this, Delphi is able to detect your provisioning profiles on your Mac, automatically; a real time saver.

One thing I noticed (however I haven’t really used yet), is the ability to hide non-visual components. I expect that is a welcome addition for many, as I’ve seen a number of people asking for it for years now.

I know this part isn’t exactly new, however FireDAC rates a mention: this is one of the best acquisitions that Delphi developers could wish for. It makes connecting to, and using, many databases, so easy.

Something I am planning to sink my teeth into: native listviews and scrollboxes for iOS. Hopefully the number of native implementations will soon grow.

For a complete list of what’s new, visit the what’s new page

More later…

The post Some stuff about Delphi 10 Seattle appeared first on Delphi Worlds.

A leg up for using ALAssetsLibrary in Delphi

$
0
0

Some time back, someone asked on the Embarcadero forums about using ALAssetsLibrary from the Assets Library framework in iOS with Delphi. I was curious because I thought I may be interested in using it myself.

It seemed relatively simple enough: create an Assets Library instance (TALAssetsLibrary from the iOSapi.AssetsLibrary unit), enumerate the groups, and for each group, enumerate the assets. That is until I discovered it wasn’t possible to move past 1st base: calling enumerateGroupsWithTypes was (at the time) causing a crash.

Recently I decided to revisit the issue, and this time the problem was an Invalid Typecast error. After a little digging, I came across someone in a similar situation in this post on stack overflow. He solved the problem by changing the definition for the “block” so that the method parameters all have pointer types.

I changed the method parameters to pointer types myself, and voila! I was able to call enumerateGroupsWithTypes without an error: made it past first base. Next step was to see what I actually have in those pointers. I figured the ALAssetsGroup parameter reference might be an objective-c object id, so I Wrap’d it using TALAssetsGroup, and I was able to successfully use the valueForProperty method. Success! It also turns out that the PBoolean really is one: it just can’t be passed via the block that way; it needs to be typecast inside the method.

The next problem is that similar action needs to be taken for ALAssetsGroup and ALAsset, both of which use block parameters.

Now for the really bad news: ALAssetsLibrary is deprecated (since iOS 9). The preferred method is to use Photos Framework, for which there is no translation available (at least out of the box) for Delphi. I have this task on my to-do list, however it is moving ever further downwards as other tasks are becoming more pressing.

Regardless, here’s some example code fragments that will give those who are also looking to use it, to give them a “leg up”:

uses
  iOSapi.AssetsLibrary, iOSapi.Foundation, Macapi.ObjectiveC;

type
  // A copy of what's in iOSapi.AssetsLibrary, but changed to Pointer parameters
  TALAssetsLibraryGroupsEnumerationResultsBlock = procedure(group: Pointer; stop: Pointer) of object;
  TALAssetsLibraryAccessFailureBlock = procedure(error: Pointer) of object;

  ALAssetsLibrary = interface(NSObject)
    ['{F6073848-1D90-456C-8785-73BEC411D3B6}']
    procedure enumerateGroupsWithTypes(types: ALAssetsGroupType; usingBlock: TALAssetsLibraryGroupsEnumerationResultsBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure assetForURL(assetURL: NSURL; resultBlock: TALAssetsLibraryAssetForURLResultBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure groupForURL(groupURL: NSURL; resultBlock: TALAssetsLibraryGroupResultBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure addAssetsGroupAlbumWithName(name: NSString; resultBlock: TALAssetsLibraryGroupResultBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure writeImageToSavedPhotosAlbum(imageRef: CGImageRef; orientation: ALAssetOrientation; completionBlock: TALAssetsLibraryWriteImageCompletionBlock); cdecl; overload;
    procedure writeImageToSavedPhotosAlbum(imageRef: CGImageRef; metadata: NSDictionary; completionBlock: TALAssetsLibraryWriteImageCompletionBlock); cdecl; overload;
    procedure writeImageDataToSavedPhotosAlbum(imageData: NSData; metadata: NSDictionary; completionBlock: TALAssetsLibraryWriteImageCompletionBlock); cdecl;
    procedure writeVideoAtPathToSavedPhotosAlbum(videoPathURL: NSURL; completionBlock: TALAssetsLibraryWriteVideoCompletionBlock); cdecl;
    function videoAtPathIsCompatibleWithSavedPhotosAlbum(videoPathURL: NSURL): Boolean; cdecl;
  end;
  TALAssetsLibrary = class(TOCGenericImport<ALAssetsLibraryClass, ALAssetsLibrary>)end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    FAL: ALAssetsLibrary;
    procedure GroupsEnumerationResults(group: Pointer; stop: Pointer);
    procedure AccessFailure(error: Pointer);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  Macapi.Helpers;

{ TForm1 }

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  FAL := TALAssetsLibrary.Create;
end;

destructor TForm1.Destroy;
begin
  FAL.release;
  inherited;
end;

procedure TForm1.AccessFailure(error: Pointer);
begin
  //
end;

procedure TForm1.GroupsEnumerationResults(group: Pointer; stop: Pointer);
var
  LGroup: ALAssetsGroup;
  LName: NSString;
  LNameString: string;
  LStop: Boolean;
begin
  LStop := PBoolean(stop)^;
  if not LStop then   // I put a break point here to check the value of LStop
    Sleep(0); 
  LGroup := TALAssetsGroup.Wrap(group);
  LName := TNSString.Wrap(LGroup.valueForProperty(CocoaNSStringConst(libAssetsLibrary, 'ALAssetsGroupPropertyName')));
  LNameString := NSStrtoStr(LName);
  if LNameString = '' then  // I put a break point here too to check LNameString
    Sleep(0);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FAL.enumerateGroupsWithTypes(ALAssetsGroupAll, GroupsEnumerationResults, AccessFailure);
end;

The post A leg up for using ALAssetsLibrary in Delphi appeared first on Delphi Worlds.

Fixing the beta entitlements issue for submission to Test Flight

$
0
0

A change (as to exactly when, I don’t know) in Apple’s requirements for apps submitted to Test Flight (via iTunesConnect) has been causing an issue for those using Delphi, as there is a key missing from the .plist contained in the .ipa file. The issue is evident when assigning a build to external testers, namely that iTunesConnect reports: “Some builds are missing the beta entitlement, or were uploaded with a beta version of Xcode, and cannot be tested”

A way around this is to use the iOS9Fix from TMS to add the missing key to the .plist file. Make sure you follow the instructions in the TMS article, and simply add the following to the end of the iOS9Fix_config.txt file:

<key>DTSDKBuild</key>
<string>!BuildNumber!</string>

Where !BuildNumber! corresponds to the DTPlatformBuild key in the <project>.info.plist file which is in the iOSDevice64\release folder when you build your app, where <project> is the name of your Delphi project.

Note that if you don’t require the transport security fix or have Delphi 10 Seattle Update 1 (because that resolves that particular issue), you should include only the DTSDKBuild key and string in the iOS9Fix_config.txt file.

The post Fixing the beta entitlements issue for submission to Test Flight appeared first on Delphi Worlds.


Making the Location Sensor Work in the Background on iOS 9

$
0
0

A recent post in the Embarcadero Delphi forums asked about why the location sensor stopped working in the background when changing to iOS 9. Since I have an interest in this area, I’ve done a bit of investigating as to how to make this work, and this post is the result.

The location sensor stopped working because of a change in iOS 9 that requires a property to be set on CLLocationManager, in addition to having already include location in UIBackgroundModes in the project options. The property in question is allowsBackgroundLocationUpdates, however this property is not declared CLLocationManager in the iOSapi.CoreLocation unit, and so of course it is not surfaced as an option in TLocationSensor. A secondary issue relates to being able to set the “always” authorization instead of “when in use”.

In order to fix most issues such as this, I prefer to modify only the implementation sections of the units provided with Delphi. This is because I need only include those units when recompiling, rather than all of the units that are affected by a change if one is made in the interface section. This practice made this issue a challenge because I needed to change the interface provided in one unit (iOSapi.CoreLocation) that is used by another (System.iOS.Sensors). The answer was to completely redeclare CLLocationManager in System.iOS.Sensors in the implementation section, and add the new methods for the property there. Since the delegate is also declared in iOSapi.CoreLocation, I needed to redeclare that, too.

Here’s snippets (for brevity, and to avoid copyright issues) of the changes, which appear just after the uses clause in the implementation section of System.iOS.Sensors:

type
  // Reintroducing the entire CLLocationManager and CLLocationManagerDelegate in order to add allowsBackgroundLocationUpdates property
  CLLocationManager = interface(NSObject)
    ['{B162A514-4334-48B8-B31A-32926B758340}'] // New GUID
    function activityType : CLActivityType; cdecl;
    procedure allowDeferredLocationUpdatesUntilTraveled(distance: CLLocationDistance; timeout: NSTimeInterval); cdecl;
    function allowsBackgroundLocationUpdates: Boolean; cdecl; // New method for allowsBackgroundLocationUpdates property
    function delegate: Pointer; cdecl;
// snip
    procedure setActivityType(activityType: CLActivityType); cdecl;
    procedure setAllowsBackgroundLocationUpdates(allowsBackgroundLocationUpdates: Boolean); cdecl; // New method for allowsBackgroundLocationUpdates property
    procedure setDelegate(delegate: Pointer); cdecl;
// snip
  end;
  TCLLocationManager = class(TOCGenericImport&lt;CLLocationManagerClass, CLLocationManager&gt;)  end;

  CLLocationManagerDelegate = interface(IObjectiveC)
    ['{113A227F-AD2D-4983-83C3-C5158F394257}'] // New GUID
    procedure locationManager(manager: CLLocationManager; didFailWithError: NSError); overload; cdecl;
// snip
    [MethodName('locationManager:didFinishDeferredUpdatesWithError:')]
    procedure locationManagerDidFinishDeferredUpdatesWithError(manager: CLLocationManager; error: NSError); cdecl;
  end;

The next task was to work out a way of being able to optionally set the “always” authorization, and to optionally set the allowsBackgroundLocationUpdates property when the sensor is active. To achieve this, I modified TiOSLocationSensor.DoStart and used conditional defines so that the correct code is included at compile time:

function TiOSLocationSensor.DoStart: Boolean;
var
  I: Integer;
begin
  if TOSVersion.Check(8) and (FLocater <> nil) then
  begin
    // *** Added this conditional define
    {$IF Defined(REQUESTALWAYS)}
    FLocater.requestAlwaysAuthorization
    {$ELSE}
    FLocater.requestWhenInUseAuthorization;
    {$ENDIF}
  end;
  // *** Added this if block:
  if TOSVersion.Check(9) and (FLocater <> nil) then
  begin
    {$IF Defined(BACKGROUNDUPDATES) and Defined(CPUARM64)} // for some reason, this function crashes in 32-bit
    FLocater.setAllowsBackgroundLocationUpdates(True);
    {$ENDIF}
  end;
  // check authorization
  if Authorized = TAuthorizationType.atUnauthorized then
    SensorError(SLocationServiceUnauthorized);
// snip
end;

NOTE: Where the comment “//snip” appears, portions of the code have merely been left out in that section.

To test this all out, I’ve uploaded a test project:

 

You will still need to copy System.iOS.Sensors from your source folders, to somewhere in your project path, and make the changes described above. If you’re creating your own project, also ensure you have the conditional defines set, as well as location in the UIBackgroundModes option in the Version Information section of the project options.

The post Making the Location Sensor Work in the Background on iOS 9 appeared first on Delphi Worlds.

Building OpenSSL dylibs for iOS simulator

$
0
0

NOTE: This article relates to using dylibs with iOS simulator only. If you’re building your app for iOS device, you should link against the static libraries. See this link.

Firstly, a couple of thanks: To Doron Adler, who created a fork of a script, of which mine is based on, and who gave some very helpful tips. Also to Jeremy Huddleston from Apple, who also gave some helpful advice.

If you’re like me, you still like to use Indy components for your network connectivity. They cover a wide range of protocols, and they work on Windows, OSX, iOS and Android.

I’m in the process of rebuilding a cross-platform newsreader I started some time ago, and as part of that process, I’m testing functionality on all platforms. For iOS, I prefer to test on simulator first, since I don’t have to have a physical device connected, and I can see how things look on any iOS device.

Somewhere along the line (perhaps when Xcode 7 was released), Apple stopped shipping OpenSSL dylibs that work on iOS simulator. This presents a problem as Indy uses OpenSSL for SSL connections. To add to the woes, the compiler for iOS simulator does not bind statically to .a files (think of them like .DCUs, but for OSX/iOS), so the only choice when using SSL and Indy became to test on the actual device.

Not satisfied with this situation, I set about finding out how to build dylibs for OpenSSL for iOS simulator. To cut a long story short, however many agonising hours later, I managed to come up with relatively painless solution.

Firstly, you should create a working folder somewhere on your Mac, eg /Users/<username>/Source/OpenSSLiOS, where <username> is the username you log into your Mac with.

SourceFolder

Into it, copy the script that you can download from here:

Download the OpenSSL source from here, or for older versions: here, and put it in the same folder as the script. Expand the OpenSSL .gz file, so now you will have a script file, the .gz file and a folder with the OpenSSL source.

SourceFolderOpenSSL

For the modifications that follow, I find Sublime Text is an excellent editor. You can just use TextEdit, if you so choose.

If you’re using a different version of OpenSSL from that which the script is built for, you’ll need to modify the OPENSSL_VERSION= line in the script to match.

NOTE: Some servers have not had their SSL updated (to protect against Logjam attacks <link>), and connecting to them using later versions of OpenSSL may fail. e.g. the SSL on Embarcadero’s news server (this may change after this article is published). In order to successfully connect to these I used version 1.0.2a of OpenSSL. The latest version (1.0.2g at time of writing) may work on other servers.

Next, modify the Makefile.shared file that’s in the root of the folder which contains the OpenSSL source.

Makefile.shared

The line that needs to be modified looks like this:

  SHAREDFLAGS="$$SHAREDFLAGS -install_name $(INSTALLTOP)/$(LIBDIR)/$$SHLIB$(SHLIB_EXT)"; \

Replace $(INSTALLTOP)/$(LIBDIR)/ with @executable_path, so that the line looks like this:

  SHAREDFLAGS="$$SHAREDFLAGS -install_name @executable_path/$$SHLIB$(SHLIB_EXT)"; \

The @executable_path value means that the system will expect the dylib to be in the same location as the executable. We’ll see why this is important when deployment is covered.

Next, the script needs to have its properties changed so that it can be executed. Using the Terminal app,

Terminal

change folder to the one created earlier to put the script in, e.g:

cd /Users/<username>/Documents/OpenSSLiOS

where <username> is the username you log into your Mac with, then use the following command:

chmod +x openssl-build-shared.sh

..and execute the script:

sudo ./openssl-build-shared.sh

ChmodExecute

This will take about 5-10 minutes depending on your machine. When it has finished successfully, the resulting dylibs will be in the lib/iOS folder, which is off of the folder where the script is located.

BuiltDylibs

If necessary, copy/move these files to a location where they can be included in your Delphi project to be deployed with your app. In my setup, I have a folder shared to a VirtualBox VM that has Delphi 10 Seattle installed, so I can share files between my Mac and the VM.

In your Delphi project, bring up the deployment manager. Click the Add files button

DeployDylibsAdd

Navigate to the folder containing the dylibs, select both of them and click OK

DeployDylibs

The remote path (.\) is the same folder as the application, so this does not need to be changed (same as the @executable_path in the makefile, described earlier)

DeployDylibsPaths

In your code, somewhere before it attempts to make an SSL connection you will need to set the OpenSSL search path for Indy, when compiling for the iOS simulator:

  IdOpenSSLSetLibPath(TPath.GetDirectoryName(ParamStr(0)));

This sets the path to the dylibs as the executable directory.

I hope this has been of some use. It was certainly satisfying for me to finally make this work, and I wanted to share it with others.

The post Building OpenSSL dylibs for iOS simulator appeared first on Delphi Worlds.

Debugging from Delphi in a VM using an emulator on the host

$
0
0

Note: These instructions work for me, where I am using VirtualBox on OSX, using a standard emulator from the Android SDK, and a Windows 10 VM with Delphi 10.1 Berlin installed.

Way back in 2013, Jim McKeeth from Embarcadero blogged about debugging against a remote Android emulator. His method uses PuTTY and remote login via SSH inside the VM, which is a little involved to set up, however once it is, it’s relatively painless to use.

Recently there was a question on StackOverflow about connecting to the Nox player, which I was interested in using, so I did a bit of digging and found an answer. The comments after the solution had me curious: if adb can “connect” to the emulator, surely this is a method that could be used for debugging from Delphi? After a little more digging, I came up with a method that can be kicked off from the Delphi IDE.

One of Delphi’s features is being able to run external tools from the IDE, using Tools|Configure Tools

Delphi10BerlinToolsOptions

This method can run any external command of your choosing, and more importantly in this case (i.e. it appears to work only this way), the command is executed as a process spawned from the Delphi executable. In order to make the emulator on the host visible to Delphi, use the Add button to create a new entry, and enter the details as per the next image:

Delphi10BerlinToolsOptionsADBToMac

(the value for Program on my machine is: C:\Users\Public\Documents\Embarcadero\Studio\18.0\PlatformSDKs\android-sdk-windows\platform-tools\adb.exe)

The value for the path to adb.exe in the Program field may differ depending on your version of Delphi, and version of Windows. The path in the image is for Delphi 10.1 Berlin on Windows 10. The Parameters value is the command for adb, which in this case is connect, and the IP address (10.0.2.2) of the special alias for the loopback interface in the emulator. Click OK, and the item added becomes available from the Tools menu in the IDE.

Make sure the emulator is actually running in your host (in my case OSX), then in the Delphi IDE use the Tools menu to run the command that has been added. Now go to the Project Manager, right-click the Target node under Android, click Refresh,

Delphi10BerlinProjectTargetRefresh

and the emulator should appear under it. This is how it appears for me:

Delphi10BerlinProjectTargetEmulator

Now you’re set to debug against the Android emulator that is running on your host machine.

The post Debugging from Delphi in a VM using an emulator on the host appeared first on Delphi Worlds.

Allowing an iOS app to run in the background

$
0
0

NOTE: This article is about allowing your iOS app to run when it goes into the background (i.e. another app becomes active) for a short period (up to 3 minutes on iOS 9, at least). It does not relate to having the UIBackgroundModes option, an example of which is here.

As some of you will already know, some methods on iOS classes which have been “imported” into Delphi, have been left out, for whatever reason that might be, and some are quite unfortunate, because it means having to work around it, as per the example I linked to, above.

This is the case for UIApplication, where two methods related to enabling an iOS app to run when the app is in the background, have been left out; namely:


function beginBackgroundTaskWithExpirationHandler(handler: TBackgroundTaskHandler): UIBackgroundTaskIdentifier; cdecl;
procedure endBackgroundTask(identifier: UIBackgroundTaskIdentifier); cdecl;

I knew of this omission a while ago, and a recent post on Google+ prompted me to look into it in more depth. The two methods seemed easy enough to use, so I put together a class that wraps the 2 calls and makes using them a bit more convenient. The result is in the demo that follows at the end of this article. Rather than going too deep into explanations, I’ll allow the reader to have a look at it, and I’ll field questions if necessary 🙂

The post Allowing an iOS app to run in the background appeared first on Delphi Worlds.

Performing background fetches on iOS

$
0
0

This article relates to any version of Delphi (or at least it should work) that can target iOS 7 or greater. The demo project was created using Delphi 10.1 Berlin.

There’s been the odd post to the Embarcadero forums as well as on StackOverflow about how to implement background fetches on iOS. Partly because it’s not an every day problem, and partly because it involves a little “trickery” to make it work. In one such question on StackOverflow, the poster actually ended up solving the problem themselves. I figured that I might want to use the solution later, and in case it was doing more than was necessary, set out to investigate whether it was possible using a little less “jumping through hoops”.

Sadly, it appears the only method that does work is by using their solution, however I’ve repackaged it to allow it to become “cross-platform”. also in a way that the method of calling the completion handler is separated from the background fetching code, so that it might be used elsewhere. A more detailed explanation follows:

Background fetches are a method introduced into iOS 7, whereby an application can be in the background, and periodically iOS will call a method in the application that allows a period of up to 30 seconds in order to for example, retrieve some data from the internet.

In order for applications to be enabled for background fetch, the “fetch” option needs to be checked in the UIBackgroundModes key in the Version Info section in the project options:

ProjectOptionsVersionInfoUIBackgroundModesFetch

The method that iOS calls is named performFetchWithCompletionHandler, and is supposed to be implemented by the application delegate.

Problem #1: in FMX, this method has not been added. Fortunately, there’s a way of adding missing methods to the application delegate, using the Objective-C runtime function: class_addmethod.

Problem #2: When performFetchWithCompletionHandler is called, it passes a reference to what is called an Objective-C “block”, in the handler parameter. The trick is to convert the “block” into a method reference. Fortunately, the Objective-C runtime also has a function: imp_implementationWithBlock, that takes the block as a parameter, and returns a method reference.

For problems #1 and #2, I’ve put together a unit (ObjCRuntimePlus in the demo) that takes all this detail and wraps it up into something (hopefully) re-usable.

Problem #3: When an iOS app has been configured for background fetch, a desired interval needs to be set, because the default is for iOS to “never” call the background fetch. This interval is set by the setMinimumBackgroundFetchInterval method on UIApplication. The problem is that this method is not declared on UIApplication in the iOSapi.UIKit unit. Fortunately (again), one solution is to redeclare UIApplication in the background fetch code (in the BackgroundFetch.iOS unit in the demo), with only the methods that are needed. Going by my online research, setMinimumBackgroundFetchInterval should be called when the FinishedLaunching application event is sent, however it is possible that it could be called later.

The end result is in the demo project that follows. I’ve implemented the background fetch as a platform service (makes use of TPlatformServices), where it’s simply a case of obtaining the service, setting the OnFetch handler, and providing an implementation for it.

In order to see it in action, run the application, put iOS into lock mode, and wait around 5 minutes (it could be longer, as it can vary). I’m leaving it up to readers to implement the actual fetch when OnFetch is called. Remember though, that the fetch is limited to a maximum of 30 seconds.

The post Performing background fetches on iOS appeared first on Delphi Worlds.

Region monitoring, including background on iOS

$
0
0

Get started with region monitoring (including in the background) on iOS with some how-to’s on fixing the Delphi RTL source for the LocationSensor.

In an earlier article, I described some changes you’d need to make in order to make monitoring of location changes work in the background with Delphi 10 Seattle. This article is aimed at Delphi 10.1 Berlin, where a couple of fixes have been made by Embarcadero since Delphi 10 Seattle, however with a little bit of work, should be able to be back ported to at least Seattle.

Not long ago I was asked by Marcelo Carvalho (a Delphi developer from Brazil) if I could look into fixing region monitoring, because he was having trouble adding regions to the LocationSensor once it was active, and also wanted to be able to monitor location changes for extended periods of time (> 12 hours). I asked Marcelo what the name of the project he was working on so I had something to refer to, and he said it wasn’t anything very specific yet, and since it is based on the “geofence” concept, he dubbed it “MonkeyFence”. He also wanted to pay forward the work to the Delphi community, so here it is.

The trouble starts in the System.iOS.Sensors unit: when a region is added to the sensor, the RegionAdded method is called. The region item passed in is “converted” to a CLRegion using the ConvLocationRegion method. In that method, a native CLRegion is created and initCircularRegionWithCenter is called.

Problem #1: The declaration for initCircularRegionWithCenter is wrong – the third parameter is declared in the Objective-C header as (NSString *), which is a pointer to an NSString, not an NSString itself.

Problem #2: initCircularRegionWithCenter has been deprecated since iOS 7, so unless you’re really targeting devices below that, then a CLCircularRegion should be used.

Problem #3: The initWithCenter method in CLCircularRegion is also declared incorrectly – I guess at least there’s consistency 😉

To start fixing these issues, copy System.iOS.Sensors.pas to somewhere in your project path and modify it, to redeclare CLCircularRegion with the correct method signature (you’ll note all my modifications are prefixed with a // MonkeyFence comment, and // snip appears where I have snipped the original source):


type
  // MonkeyFence - redeclared to correct the parameter list for initWithCenter
  CLCircularRegionClass = interface(CLRegionClass)
    ['{B2E71730-FB37-4DB4-9D49-8A004BB6C62C}']
  end;

  CLCircularRegion = interface(CLRegion)
    ['{FF4DCF91-376B-41BB-B60A-880BEBB5B4EE}']
    function initWithCenter(center: CLLocationCoordinate2D; radius: CLLocationDistance; identifier: Pointer): Pointer; cdecl;
    function center: CLLocationCoordinate2D; cdecl;
    function radius: CLLocationDistance; cdecl;
    function containsCoordinate(coordinate: CLLocationCoordinate2D): Boolean; cdecl;
  end;
  TCLCircularRegion = class(TOCGenericImport<CLCircularRegionClass, CLCircularRegion>)  end;

Modify ConvLocationRegion to use TCLCircularRegion:


function ConvLocationRegion(const Region: TLocationRegion): CLRegion;
var
  Center: CLLocationCoordinate2D;
  UniqueID: NSString;
  // MonkeyFence
  LCircularRegion: CLCircularRegion;
begin
  Center := CLLocationCoordinate2DMake(Region.Center.Latitude, Region.Center.Longitude);
  // MonkeyFence
  UniqueID := StrToNSStr(Region.ID); //  TNSString.Wrap(TNSString.OCClass.stringWithUTF8String(MarshaledAString(UTF8Encode(Region.ID))));

  // create the region object and add it for monitorization
  // MonkeyFence - Deprecated method, and method signature is wrong anyway
  // Result := TCLRegion.Wrap(TCLRegion.Create.initCircularRegionWithCenter(Center, Region.Radius , UniqueID));
  // MonkeyFence
  LCircularRegion := TCLCircularRegion.Create;
  LCircularRegion.initWithCenter(Center, Region.Radius, (UniqueID as ILocalObject).GetObjectID);
  Result := LCircularRegion;
end;

Problem #4: TiOSLocationDelegate has a couple of pieces missing, namely the MethodName attributes for locationManagerDidEnterRegion and locationManagerDidExitRegion. Without them, those methods are never called – pretty important if the events are going to be fired:


// snip
  public
    { CLLocationManagerDelegate }
    procedure locationManager(manager: CLLocationManager; didChangeAuthorizationStatus: CLAuthorizationStatus); overload; cdecl;
    [MethodName('locationManager:didEnterRegion:')] // MonkeyFence
    procedure locationManagerDidEnterRegion(manager: CLLocationManager; region: CLRegion); cdecl;
    [MethodName('locationManager:didExitRegion:')] // MonkeyFence
    procedure locationManagerDidExitRegion(manager: CLLocationManager; region: CLRegion); cdecl;
    procedure locationManager(manager: CLLocationManager; didFailWithError: NSError); overload; cdecl;
// snip

In my earlier article, I used a conditional define to signify if the app is to use the request always permission. Now through the magic of custom properties, the app will set the permission this way:


// MonkeyFence - put this before TiOSLocationSensor.DoStart 
function LocationRequestAlwaysPermission: Boolean;
var
  LBundle: NSBundle;
  LPointer: Pointer;
begin
  Result := False;
  LBundle := TNSBundle.Wrap(TNSBundle.OCClass.mainBundle);
  LPointer := LBundle.infoDictionary.valueForKey(StrToNSStr('MFLocationRequestAlwaysPermission')); // Do not localise
  if LPointer = nil then
    Exit; // <======
  Result := NSStrToStr(TNSString.Wrap(LPointer)).Equals('true'); // Do not localise
end;

Using a similar technique, the standard UIBackgroundModes property can be read so that setAllowsBackgroundUpdates can be called based on the property values:


// MonkeyFence - put this before TiOSLocationSensor.DoStart, too
function HasBackgroundMode(const AMode: string): Boolean;
var
  LBundle: NSBundle;
  LPointer: Pointer;
  LModesArray: NSArray;
  LModeString: string;
  I: Integer;
begin
  Result := False;
  LBundle := TNSBundle.Wrap(TNSBundle.OCClass.mainBundle);
  LPointer := LBundle.infoDictionary.valueForKey(StrToNSStr('UIBackgroundModes')); // Do not localise
  if LPointer = nil then
    Exit; // <======
  LModesArray := TNSArray.Wrap(LPointer);
  for I := 0 to LModesArray.count - 1 do
  begin
    LModeString := NSStrToStr(TNSString.Wrap(LModesArray.objectAtIndex(I)));
    if AMode.Equals(LModeString) then
      Exit(True); // <======
  end;
end;

Now TiOSLocationSensor.DoStart can be modified this way:


function TiOSLocationSensor.DoStart: Boolean;
var
  I: Integer;
begin
  // MonkeyFence
  if TOSVersion.Check(6) and (FLocater <> nil) then
  begin
    // Would be nice to have these as configurable within the application
    // https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instp/CLLocationManager/pausesLocationUpdatesAutomatically
    //!!!! Can cause high battery consumption, but may be required for updates while in lock screen
    // FLocater.setPausesLocationUpdatesAutomatically(False);
    // https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instp/CLLocationManager/activityType
    // Makes sure the location updates pause if the user is stationary for a while
    FLocater.setActivityType(CLActivityTypeFitness);
  end;
  if TOSVersion.Check(8) and (FLocater <> nil) then
  begin
    // MonkeyFence
    if LocationRequestAlwaysPermission then
      FLocater.requestAlwaysAuthorization
    else
      FLocater.requestWhenInUseAuthorization;
  end;
  // MonkeyFence
  if TOSVersion.Check(9) and (FLocater <> nil) and HasBackgroundMode('location') then
  begin
    {$IF Defined(CPUARM64)} // for some reason, this function crashes in 32bit
    FLocater.setAllowsBackgroundLocationUpdates(True);
    {$ENDIF}
  end;
// snip

Problem #6: So now that locationManagerDidEnterRegion and locationManagerDidExitRegion are being called, yet another omission needs to be fixed, namely that the region identifier is not included in the TLocationRegion when it is initialised:


procedure TiOSLocationDelegate.locationManagerDidEnterRegion(manager: CLLocationManager; region: CLRegion);
var
  LRegion: TLocationRegion;
begin
  DoStateChanged(TSensorState.Ready);
  try
    LRegion := TLocationRegion.Create(ConvCLLocationCoord(region.center), region.radius, NSStrToStr(region.identifier)); // MonkeyFence
// snip

procedure TiOSLocationDelegate.locationManagerDidExitRegion(manager: CLLocationManager; region: CLRegion);
var
  LRegion: TLocationRegion;
begin
  DoStateChanged(TSensorState.Ready);
  try
    LRegion := TLocationRegion.Create(ConvCLLocationCoord(region.center), region.radius, NSStrToStr(region.identifier)); // MonkeyFence
// snip

Phew! That’s all the changes needed for System.iOS.Sensors.

Problem #7: After I had worked out the fixes for this unit, Marcelo said he was also having trouble removing regions (using the RemoveRegion method of TLocationSensor), i.e. that it wouldn’t remove them at all. Long story short, another fix was required for the System.Sensors unit. Use the same process as for System.iOS.Sensors, i.e. make a copy and put it somewhere in your project path, then make the following changes – in the implementation uses clause:


uses
  System.Variants, System.Math, System.Character, System.Generics.Defaults, // MonkeyFence - System.Generics.Defaults added

..and in TCustomLocationSensor.Create:


constructor TCustomLocationSensor.Create(AManager: TSensorManager);
begin
  inherited;
  // MonkeyFence - fixes the comparison for the Contains method of TRegionList
  FRegions := TRegionList.Create(TDelegatedComparer<TLocationRegion>.Create(
    function(const ALeft, ARight: TLocationRegion): Integer
    begin
      Result := 1;
      if (ALeft.ID = ARight.ID) and (ALeft.Radius = ARight.Radius) and (ALeft.Center.Latitude = ARight.Center.Latitude)
        and (ALeft.Center.Longitude = ARight.Center.Longitude) then
        Result := 0;
    end)
  );
  FRegions.OnNotify := RegionNotify;
end;

I count those changes as a workaround, because I was short on time and just needed to make removing regions work. If/when I have more time, I’ll revisit the actual cause, however it’s likely to do with the default comparer for record types.

Given that almost none of the code for region monitoring actually works, it’s an indicator of how much testing was done for it.

Now for the demo. Note that this demo really is a test project – it was pretty much thrown together in a couple of days or so, though you may note it leans towards using a MVVM type of arrangement, which is growing on me 🙂

A couple of things to note: it uses a UIBackgroundModes value of “location”, and a custom value of MFLocationRequestAlwaysPermission (as mentioned earlier), for all build configurations in iOS Device 32bit, iOSDevice 64bit and iOS Simulator.

It also includes a few random locations that are added to the sensor as regions when the app starts up, so if you fire it up in the simulator, then use Debug|Location (in the simulator) to change the location to somewhere inside the region, then outside the region, you’ll see the region enter/exit events in the app, in the Events window, and in the simulator log (Debug|Open System Log in the simulator)

As per other demos I have posted, rather than go too much into detail about the demo itself, I’ll leave it up to readers to ask questions here in the comments.

The post Region monitoring, including background on iOS appeared first on Delphi Worlds.

Checking if wifi is enabled on iOS

$
0
0

Here I present some simple code to check if wifi is enabled on iOS, which could be used to alert the user if your application uses location services.

One of my most visited posts is from 3 years ago, which has some demo code for checking whether the internet is accessible from the device. The code presented here can detect if wifi is enabled, which is related, in a way.

Remember that devices could also access the internet via the cellular network, so this check can’t really be used as a precursor to the method used in the other article. On the other hand, if your application uses location services, then you could use this code to alert the user that your app may not be as accurate as it would otherwise be, and that they may want to turn the wifi back on.

Those who visited my last article may recognise this code, since it comes from the demo in that article. During the testing process, I had inadvertently left the wifi turned off (probably because the network I was connected to had poor performance), and had not turned it on later. When I was checking the location updates in the demo, I noticed they were not as reliable as before.

I remembered that other “built-in” iOS apps themselves sometimes warn that location updates may be more reliable if wifi is turned on, so I set about finding how to determine if it is. I came across the article listed in the code below, translated it into Delphi, and voila!

Here’s the code:


uses
  System.SysUtils, Posix.Base, Posix.NetIf, Macapi.ObjCRuntime, Macapi.ObjectiveC, Macapi.Helpers, iOSapi.Foundation;

const
  cWifiInterfaceName = 'awdl0'; // That's a small L (l), not a one (1)

function getifaddrs(var ifap: pifaddrs): Integer; cdecl; external libc name _PU + 'getifaddrs';
procedure freeifaddrs(ifap: pifaddrs); cdecl; external libc name _PU + 'freeifaddrs';

function StrToNSStringPtr(const AValue: string): Pointer; 
begin 
  Result := (StrToNSStr(AValue) as ILocalObject).GetObjectID; 
end; 

// Translated from here: http://www.enigmaticape.com/blog/determine-wifi-enabled-ios-one-weird-trick 
function IsWifiEnabled: Boolean; 
var 
  LAddrList, LAddrInfo: pifaddrs; 
  LSet: NSCountedSet; 
begin 
  Result := False; 
  if getifaddrs(LAddrList) = 0 then 
  try 
    LSet := TNSCountedSet.Create; 
    LAddrInfo := LAddrList; 
    repeat 
      if (LAddrInfo.ifa_flags and IFF_UP) = IFF_UP then 
        LSet.addObject(TNSString.OCClass.stringWithUTF8String(LAddrInfo.ifa_name)); 
      LAddrInfo := LAddrInfo^.ifa_next; 
    until LAddrInfo = nil; 
    Result := LSet.countForObject(StrToNSStringPtr(cWifiInterfaceName)) > 1;
  finally
    freeifaddrs(LAddrList);
  end;
end;

The post Checking if wifi is enabled on iOS appeared first on Delphi Worlds.


Preview of using iOS Photos Framework with Delphi

$
0
0

Here I present a video of the results of some work I’ve been doing with the iOS Photos Framework with Delphi

A while ago I posted about using the ALAssetsLibrary in iOS. In that article, I briefly mentioned the Photos Framework, which is now the preferred method of managing media stored on iOS devices (i.e. ALAssetsLibrary is deprecated, since iOS 8.0, not 9.0, as I had previously stated)

Since the headers for the Photos Framework are not supplied with Delphi (at least at time of writing), in order to use the framework it would normally be a daunting task of manually converting the .h files into Delphi. When Delphi 10.1 Berlin was released, it shipped with a tool called SDKTransform, which automates (for the most part, and not always perfectly) the conversion of the SDK header files. The task is made even easier using the excellent SDKTransform Assistant, written by Hosakawa Jun. A bit of tweaking later, and I had a unit with all the definitions I needed for the iOS Photos Framework.

However, it is one thing to have an API; it’s totally another to do something useful with it. I left what I had done so far, for a while, then revisited it later when I felt I needed to progress the work. I waded through the documentation, and through some examples I googled for.

Sadly, I soon came up against a major roadblock. There are two methods used to retrieve the actual images, which are part of the PHImageManager class: requestImageForAsset and requestImageDataForAsset. Both methods require what is called a “block” handler to be passed, so that it is called once the image data is ready. Using the Delphi types of methods (regular procedure and object method) for “block” handlers does not work. A lot more googling and experimentation later, and I had eventually resolved that it wasn’t going to be possible to achieve; at least using Delphi code.

The answer was to write a library in ObjectiveC, which contains a class that acts as kind of a proxy; doing the work of the methods in question, and passing the “block” handler from within it. I defined a delegate protocol in the library that I could implement in my Delphi code, so that the result could be passed on once the “block” handler was called.

Since I was able to make that work, I set about creating a demo that acts somewhat like the photo selection dialog in Facebook on iOS (this image is of that dialog, not of my work):

Screen Shot 2016-08-03 at 9.17.03 PM

The results so far of my work are in the video which follows. Note that this is preliminary work, so it has a few quirks that need to be ironed out.

My plan is to turn my work into a product (and include support for Android) for sale, hopefully being available in the next few weeks.

The post Preview of using iOS Photos Framework with Delphi appeared first on Delphi Worlds.

Handling the new iOS 10 privacy settings

$
0
0

With the introduction of iOS 10, a number of privacy settings need to be handled in your Delphi apps.

You may have arrived at this post because you’ve installed Delphi 10.1 Berlin Update 1, installed Xcode 8 on your Mac, added the iOS 10 SDK to Delphi, recompiled your app that uses the camera or other “private” service, only to find it crashes when it attempts to access that service. You’re in luck because it’s a fairly simple fix. There’s a fairly comprehensive article on the Xamarin blog site that covers the ins and outs of what privacy settings are now required, so I won’t go into that here. What I’ll describe here is what you need to do in Delphi in order to fix the problem.

In your project options, select an iOS platform that supports the services that you need to set usage descriptions for (usually just iOS Device 32 bit and/or iOS Device 64 bit), select the Version Info section, and right-click the list of options:

2016-09-21_13-00-05

Click the “Add Key” item, and enter the name of the key corresponding to the usage description:

2016-09-21_13-00-43

..and click OK. Then click into the edit area next to the entry with that key, and enter a description that’s meaningful. In my case, the camera is being used for scanning of QR codes:

2016-09-21_13-01-16

..and click OK. You’ll need to do this for each service that requires a description (see the article on the Xamarin blog site).

That’s it!

 

The post Handling the new iOS 10 privacy settings appeared first on Delphi Worlds.

Taking screenshots of your Android device using Monitor

$
0
0

This technique can be used for any Android development (or just for Android in general), however I figured Delphi developers might find this useful anyway.

While working on some changes for a client’s application, they asked for some screenshots from the device to send to the end users for their approval. My first thought was: “ugh.. I’ll need to remember how to take screenshots”, and the device is some fairly specialised hardware, so it might have been a challenge to work it out. My second thought was: “The SDK Monitor tool can help with monitoring log events, I wonder if it can take screenshots?”. The answer was: yes!

The Monitor tool in the Android SDK is (if you installed the SDK tools) in the tools folder under the root folder of SDK. Run monitor.bat and a few moments later, the Monitor tool is running (can take a little while starting for the first time on some systems):

..and here’s how the screen capture looks for my Nexus 5X for the Phone Dialer demo app:

Just click the Save button to save the file somewhere.

Delphi 10.2 Tokyo has been released!

$
0
0

Fast cross-platform development has become even more awesome with the release of Delphi 10.2 Tokyo, which includes server side support for Linux, FMX, RTL and IDE improvements, and more!

I’m especially interested in the multi-threading changes to TBitmap, TCanvas and TContext3D on mobile, as well as the threading changes on Android, and database support enhancements, and am looking forward to giving them a full workout.

But wait, there’s more! Check out what Eugene Kryukov of CrossVCL has managed to do on Linux! So, even though the official support is for servers only, third-party providers may be able bring Delphi GUI development back to life on Linux.

Check out the what’s new on the Embarcadero site, and be sure to register for the Delphi 10.2 Tokyo launch webinar, happening soon!

Handling Firebase Cloud Messaging on Android and iOS

$
0
0

Discover how to handle push notifications sent from Firebase Cloud Messaging (FCM) for Android and iOS

UPDATE: For anyone who has downloaded the demo prior to May 17th, 2017, there have been 2 changes: A UseSandbox property has been added to TPushClient, that determines whether the registration is for test apps, and activation of the service connection now happens in a separate thread, to account for threading changes in Tokyo.

A few months ago, I started writing some code that was aimed at being the basis for a job for a client (and may still end up that way), and because I also needed to handle push notifications in my own applications.

At first, it was going to use AWS (Amazon Web Services), via their Simple Notification Service (SNS) as a provider because it is a much lower cost solution than for example Kinvey, and other services, plus it supports a whole bunch of messaging services. As I dug deeper into how to connect with AWS and SNS, I saw it becoming fairly complicated, and as I was doing research about it, I stumbled across the fact that Firebase Cloud Messaging (FCM) (which used to be called Google Cloud Messaging), could handle both Android and iOS.

I started working on a solution, however it was pushed down my list of to-do’s, until I came across Jordi Corbilla’s excellent article on how to receive push notifications in Android. As I wanted to also handle receiving them on iOS, I started looking into how to achieve it. I wrote some code to do the process of registering an iOS device token and receive the FCM token back, however again it was shelved due to working on other tasks.

A post from Steven Chesser in the Embarcadero forums prompted me to take another look at it, and I finally came up with a standalone solution that works for both Android and iOS. I had always planned to make this part of the code public, so the code including a demo is at the PushClient project on Github. The demo and code was built with Delphi 10.2 Tokyo, however it should also work with XE8 and 10.1 Seattle.

Here’s a brief overview of how it works:

  • TPushClient (in the DW.PushClient unit) is responsible for creating an instance of the appropriate TPushService for the platform (either Android or iOS), and the TServiceConnection, which receives the messages.
  • When the device token becomes available,  if the platform is iOS, an instance of TRegisterFCM is created, and a request to register the iOS device token is sent asynchronously to FCM. When the FCM token comes back, the OnChange event of the TPushClient is fired, and you know you now have a valid FCM token.

You can send the FCM token to your back end (if you have one) so that you can target specific devices, however you don’t need to do keep track of them if you’re just interested in sending a message to all devices that have your app installed.

You can use the FCM console to send test messages, however you may want to have a “back end” service that does this, especially if you want to target specific devices or groups of devices. I’ll cover this in a later article.

Please read the above mentioned article from Jordi Corbilla about how to set up Firebase Cloud Messaging for your application.

Viewing all 80 articles
Browse latest View live


Latest Images