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

Plugging a couple of leaks in Firemonkey in Delphi XE5

$
0
0

Posted in PatchesUncategorized

NOTE: If you have read this post before, it has been updated recently to include further changes that remove all leaks generated by the demo project.

Recently there was a posting in the Embarcadero forums about problems with creating a secondary form, showing it modally, then destroying it; namely that the app exits without warning after showing the form a number of times. There were also no crash logs for the app. This is a classic symptom that the app suffers from a memory leak.

I created a test application, attached Instruments to the app, and sure enough, the app was leaking memory. I had also hooked in a handler for the application events so I could see if the app is receiving any low memory warnings. Please refer to the attached project that demos the problem, and also serves as an example of how to hook into application events.

Instruments is a companion application that comes with Xcode that helps debug your iOS and OS X apps. To run the application, click on the Xcode menu item, click Open Developer Tool, then click Instruments:

When Instruments starts, it prompts for a template to use. For checking Leaks in your app, select the Leaks template:

and click the Choose button. Click the Choose Target dropdown, and make sure your device is checked. To select your app, click Choose Target again. If your app is already running, you can click Attach To Process and select your app from the list of running apps:

otherwise select your app from the Choose Target submenu. In this case, your app obviously need to have been deployed to your device.

Select the Leaks category from the list of Instruments on the left, and click the red record button at the top left. I tend to uncheck Automatic Snapshotting, so that I can examine leaks on an ad-hoc basis. To turn it off, simply uncheck the checkbox:

If you have it turned off, and want to check a status of any leaks, click the Snapshot Now button. The following image is the leak status of the test project, after I’ve clicked Button 1 to open the modal window, then clicked Button 2 to close it, twice:

As can be seen, there’s a bunch of leaks, and given the count of each leak, they occur each time the form is “destroyed”. Thanks to a post by Cristian Peta in the thread mentioned earlier, to plug the TSettingsFont leak involves making a copy of the FMX.Graphics unit, placing it somewhere in the compiler search path, and making the following modification:

type
  TSettingsFont = class (TFont)
  private
    [Weak] FTextSettings: TTextSettings;  // Add the [Weak] directive on this line
  protected
    procedure DoChanged; override;

I’ve also been alerted by William Brookfield to a workaround in this QC report. Thanks also to Babak Yaghoobi for his help regarding this leak. This time make a copy of the FMX.Platform.iOS unit (or modify a copy you might have made from another patch), and make the following mods. Firstly in TPlatformCocoaTouch.ShowWindowModal:

  finally
    BackView.removeFromSuperview;
    BackView.release; // Add this line
  end;
  Result := AForm.ModalResult;
end;

..and in TPlatformCocoaTouch.DestroyWindow:

procedure TPlatformCocoaTouch.DestroyWindow(const AForm: TCommonCustomForm);
begin
  if Assigned(AForm.Handle) then
  begin
    WindowHandleToPlatform(AForm.Handle).View.removeFromSuperview;
    WindowHandleToPlatform(AForm.Handle).View.release;  // Add this line
  end;
end;

Rebuilding the app, attaching Instruments to it and recording as before results in no more leaks! I hope this has been of use, either to fix leaks in your app or just as a guide to using Instruments.

 


Adding Javascript processing capability to TWebBrowser in iOS

$
0
0

Posted in Code tipsPatchesUncategorized

UPDATE: If you’re using Delphi XE7, the TWebBrowser component now includes the method EvaluateJavascript (does the same thing as what my GetJavaScript result does), and applies to both iOS and Android.

In a post on the Embarcadero forums some time ago, Charles Vinal asked about whether it would be possible to have TWebBrowser implement the stringByEvaluatingJavaScriptFromString method which is available in UIWebView, which is inherited from its ancestor, UIView.

The short answer is yes, however it requires some modifications to existing source. You need to make copies of FMX.WebBrowser and FMX.WebBrowser.iOS, and put them in the compiler search path. Next, make the following changes – in FMX.WebBrowser, add this to ICustomBrowser:

	function GetJavaScriptResult(const ScriptName: string): string;

Next, add this to TCustomWebBrowser:

function TCustomWebBrowser.GetJavaScriptResult(const ScriptName: string): string;
begin
  if Assigned(FWeb) then
    Result := FWeb.GetJavaScriptResult(ScriptName);
end;

(make sure you add the function declaration to the class, too). Next, in FMX.WebBrowser.iOS, add this to TiOSWebBrowserService:

function TiOSWebBrowserService.GetJavaScriptResult(const ScriptName: string): string;
begin
  if Assigned(FWebView) and FWebView.canGoForward then
    Result := FWebView.stringByEvaluatingJavaScriptFromString(NSSTR(ScriptName)).UTF8String;
end;

Note: If your project uses a TTabControl component, you’ll also need to make sure that the source file FMX.TabControl.pas is in your project path.

Now in your project you’ll be able to call the function like this:

var
  JSResult: string;
begin
  JSResult := WebBrowser1.GetJavaScriptResult('SomeScript');
end;

Checking for an internet connection on mobile devices with Delphi XE5

$
0
0

Posted in Uncategorized

UPDATE (April 13th, 2015): I’ve added a separate download for a 64 bit version of libReachability.a. This file is untested as I do not have regular access to a 64 bit iOS device.

UPDATE: The project has been updated to include retrieving the current SSID (that the device is connected to). For Android, this means adding the Access Wifi State permission. Refer to the updated text in the article.

It’s a question that has been asked a few times in the Embarcadero forums: how to check if a mobile device (either Android or iOS) has an internet connection.

I already knew of a solution for iOS, and recently I came across one for Android, so I figured I would combine the two and create a platform-independent unit, called NetworkState. In that unit, I created an abstract class that could be derived from in other units that would provide the implementation for each platform, i.e. Android and iOS. The declaration for the abstract class TCustomNetworkState looks like this:

TCustomNetworkState = class(TObject)
  function GetSSID: String; virtual; abstract;
  function IsConnected: Boolean; virtual; abstract;
  function IsWifiConnected: Boolean; virtual; abstract;
  function IsMobileConnected: Boolean; virtual; abstract;
end;

As can be seen, the intention is to simply provide functions for checking if either there is a connection at all, and specifically whether there is either a Wifi or Mobile (Cellular data) connection, and what the SSID is. It was also the intention that the implementation use the supported methods for checking for connections, rather than say, using the Indy components.

The units NetworkState.iOS and NetworkState.Android each declare TPlatformNetworkState that provide the actual implementation, and the implementation uses clause of NetworkState is appropriately IFDEF’d for each platform so that the actual instance that is created is the one for the specific platform being compiled for.

I’ve included a demo project below that includes all the necessary files. If you’re going to use TNetworkState in your own project, make sure the files:

libReachability.a
NetworkState.pas
NetworkState.iOS.pas
NetworkState.Android.pas
SCNetworkReachability.pas
CaptiveNetwork.pas

are either in your project directory or in the compiler search path. Bear in mind that for the Android platform you’ll need to include the Access Network State, and Access Wifi State permissions in the project options:

For iOS on the device, you’ll need to add the SystemConfiguration framework in the SDK manager. Please refer to this article, and instead of CoreBluetooth (as per the 2nd image), use SystemConfiguration.

Product highlight: e-Restaurant Touch

$
0
0

Posted in Product highlight

Graham Murt recently posted about a product his company, Kernow Software, has released for Android and is in review for iOS, namely: e-Restaurant Touch.

Check out the product link, and the link to his post to see what he has to say about Delphi and Firemonkey.

Delphi XE5 Update 2

$
0
0

Posted in Updates news

If you haven’t done so already, I suggest you download and install Update 2 for Delphi XE5. If you apply Update 2 and you’re already using any of my patches, make sure you don’t include them when you rebuild your project, as they may conflict with the changes in Update 2, some of which I heard have been fixed.

I’ll be reviewing the differences in Update 2 over the next day or so, and will post again about changes to my patches.

Jumping into XE7

$
0
0

Posted in Uncategorized

Over the past year, I’ve been working full time on projects for a government department of my home state, South Australia. In those projects we’re using mainly jQuery, jQuery Mobile, and HTML5. I’ve also been spending a fair amount of time with my girlfriend, and playing music (I play bass guitar), so I’ve had less time to devote to Delphi.

With the release of XE7, I’ve decided to devote more time to Delphi, as some of the new features are going to have a significant impact for me. In line with this, I’m revisiting some of my articles, and either updating them or making comments about whether the issues I’ve covered have been resolved in XE7.

If you haven’t already, I very strongly suggest upgrading to XE7; this is a very significant release. I also recommend subscribing to Software Assurance, as it means you’ll receive upgrades as they’re released.

Because of my time constraints, I’ll be spending more time on XE7 here, rather than the earlier versions.

I’m just starting to jump into XE7, however I expect soon that I’ll be blogging more about this awesome release.

Making XE7 projects compile for iOS 8.0

$
0
0

Posted in Uncategorized

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).

Delphi… Out Of This World!


XE8.. bringing 64 bit goodness to iOS

$
0
0

Posted in Uncategorized

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.

Fixing a detail insert bug in FireDAC with LiveBindings

$
0
0

Posted in Code tipsPatches

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.

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

$
0
0

Posted in Uncategorized

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.

Some stuff about Delphi 10 Seattle

$
0
0

Posted in Uncategorized

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…

A leg up for using ALAssetsLibrary in Delphi

$
0
0

Posted in Code tipsProject tweaks

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;

Fixing the beta entitlements issue for submission to Test Flight

$
0
0

Posted in Uncategorized

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.

Making the location sensor work in the background on iOS 9

$
0
0

Posted in Code tipsPatches

A recent post in the Embarcadero 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 main problem comes about 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.


Building OpenSSL dylibs for iOS simulator

$
0
0

Posted in Code tipsPatchesResources

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.

A workaround for the Android app “hang” issue with Delphi 10.2.2 Tokyo

$
0
0

The recent Delphi 10.2.2 Tokyo release has resulted in a flurry of reports for apps on Android that “hang” when using tab transitions, or when using “wait” animations. This article offers an explanation for the issue, as well as a potential workaround.

Update: As per E. Spelt’s suggestion on the report, I’ve modified the TThreadedTimer class to call Synchronize, rather than Queue.

TL;DR answer: You can find the workaround at the Kastri Free project.

There were a number of changes in Delphi 10.2.2. Tokyo for Android, including an endeavour to resolve UI performance issue, and part of the changes included how timers are implemented on Android. Because of the change, when any “tight” loops are introduced such as with tab transitions or “wait” animations (e.g. when using TAnimator.AnimateFloatWait), the timer events are never fired because the Android timer code runs on the same thread.

In my investigations into the issue, I discovered that it appears there may be no way to “force” the timer(s) to check if the interval has expired and therefore fire the event, so I’ve created a workaround that “shoe-horns” a thread-based timer into the animations code. Being thread-based, when Application.ProcessMessages is called, CheckSynchronize is ultimately called, and the event that the thread-based timer has queued, is called.

As per the TL;DR answer above, I have posted the workaround in the Kastri Free project. As per the warning in the readme, any workarounds posted should be treated with caution. So far, this one is working for me.

Wishing everyone a Merry Christmas, Happy Hanukkah, Malkh, Yule, Newtonmas, Festivus 😉 or whatever you celebrate at this time of year 🙂

Monitoring location updates on Android

$
0
0

Monitoring location updates on Android in a consistent fashion (e.g in a service, and while the screen is locked) can be quite a challenge. This article tackles the problem head-on

The code discussed in this article was designed using Delphi 10.2 Tokyo Release 2, however it should at least work on Berlin, and possibly Seattle. For those who want to make a fast start, go to the Kastri Free project and download it. The demo project for this article is in the Demos\AndroidLocation folder.

Developing an app with location updates is, on the face of it, fairly straightforward. You create a new Firemonkey app, put a TLocationSensor on the form, and away you go. However, if your intention is to track the device while the app is not even running, you need to use a service. Even if you’re tracking just using an app, when the screen is locked, the situation can change. This discussion focuses on the former problem, i.e. tracking the device when the app is not even running.

These are the main points that I’ll cover:

  • Starting the service when the device is turned on, or “restarted”
  • Monitoring the device for when the screen is locked/unlocked, and when entering/exiting “doze” mode
  • Moving the service into the “foreground”, so that it still has network access even when the screen is locked
  • Catering for “doze” mode
  • Other techniques used in the demo
  • Communicating with a server

Starting the service on boot or restart

If you have a service that tracks the device location, you might want it to start when the device is turned on, or when it is restarted (which can be two different things now), so that tracking starts immediately. You may have in the past seen articles that cover being able to start an application at boot, however what if the user does not necessarily want to see the app start when the device starts?

The answer is in some Java code I have devised that acts as a “multipurpose” broadcast receiver. The code is part of the Kastri Free project, in DWMultibroadcastReceiver.java. This code will respond when the device boots, and take action depending on which intent actions are being filtered, including starting the service, and monitoring alarms.

In order to be able to start the service at boot or restart, and to monitor for the “doze” alarm, there are a couple of entries made in the manifest, as per this image:

Monitoring for screen lock and “doze” mode

When the screen is locked, after a period of time on later versions of Android, the system restricts network access to processes that are running in the foreground. To make matters worse, on later versions of Android, if the device is locked for an extended amount of time (somewhere around an hour on my Nexus 5X running Android 8.1) it can enter what is called “doze” mode, where it may not receive location updates.

To monitor for when the screen is locked or unlocked, or enters or exits “doze” mode, the service needs to register a receiver to listen for broadcasts for those specific intent actions. In the demo code, this is handled by the TServiceReceiver class, which forwards the intent on to the service.

“Moving” the service into the foreground

To alleviate the network access problem when the screen is locked, the service needs to be “moved” into the foreground. When the service detects the screen is locked, it calls StartForeground, which puts it into the foreground state. Foreground services require that a notification is supplied, which means that a notification icon would normally appear (if the screen was unlocked) in the status bar. When the screen is unlocked, the service calls StopForeground, which puts it out of foreground state. Since the service is in the foreground only when the screen is locked, the user will notice the notification only when the lock screen is viewed:

Catering for “doze” mode

When in this state, the only way to keep posting regular updates (although the device will be stationary anyway) is by use of an alarm, set with the AlarmManager, and in a special “allow when idle” mode. When using an alarm in this mode, the documentation says that it should be set at no more than once per 9 minutes.

As above, the service monitors for changes in lock mode, and when entering lock mode, sets an alarm. When the alarm goes off, the multipurpose receiver (described in the first section) “starts” the service (although in this case it has already started), and in the AndroidServiceStartCommand event updates the location using getLastKnownLocation, and resets the alarm.

Other techniques used in the demo

If you study the code, you may notice that it does not use TLocationSensor. Part of the reason for this is that it needs an instance of the Android LocationManager anyway (JLocationManager in Delphi) in order to call getLastKnownLocation, and I wanted to make sure that I had absolute control over how the listeners were set up etc. It’s quite possible that the same result could be achieved by using TLocationSensor.

Also, you may notice the use of a timer (not an FMX TTimer, though), which is set to a 4 minute interval. This is a “hangover” from experimentation of location updates when the screen is locked, and when the device enters doze mode. I have left it in, in case it might be needed in the future.

Communicating with a server

The whole point of the demo is so that when a location update is available, it can be sent to a server of some kind. In this case, it’s geared towards sending a JSON request to (presumably) a REST server. If you use this part of the demo, you could modify the cLocationUpdateURL to one of your own, and modify cLocationRequestJSON to suit your own requirements.

If you’re just interested in experimenting with the demo and don’t have your own server, you may contact me, and I can set you up to access mine.

As always, instead of explaining a lot of the code, I will field questions if you have any.

I’d like to thank Atek Sudianto, from Jakarta, Indonesia who set me down this path. I am in the planning stages of an app that requires all of what I’ve covered in this article, and his enquiries gave me the “prod” I needed.

Targeting Android 8 and higher

$
0
0

****IMPORTANT****: Please use the solution presented in my follow-up article at this link, in preference to the one presented here. This article is retained here for reference only.

From August 2018, all new apps on Google Play must target Android 8 (API level 26) or higher, and from November 2018, all app updates on Google Play must do the same. If your app also needs to request “dangerous” permissions, this article will help you achieve that.

Note: The demo in this article was devised with Delphi Tokyo 10.2.3, however it may work with earlier versions.

UPDATE: Brian Long has alerted me to the fact that using this solution, the status bar just appears as a white rectangle (i.e. no icons, time etc), something I should have noticed in my testing (too focused on problem at hand!). I’ll be looking into how to resolve this.

UPDATE 2: The files have now been updated to include an interim solution for the status bar problem. This includes:

MainFrm.pas
DW.SystemHelper.pas
DW.SystemHelper.Android.pas
dw-nativeactivity.jar

The interim solution involves setting the status bar translucent, which results in the total screen space for the app to include the status bar area, so there is now a rectangle at the top of the main form to account for this.

UPDATE 3: I’ve created another article that presents an alternative solution to “overriding” the activity, includes a workaround for an issue with TTakePhotoFromCameraAction, and an alternative solution for the status bar issue.

Up until now, by default, Delphi targets Android 4.0 (API level 14, otherwise known as Icecream Sandwich). The value (14) is inserted as the targetSdkVersion value in the AndroidManifest.xml file which is deployed with your app.

What does this mean for your apps? According to the documentation, this value tells the system it should not enable any compatibility behaviours to maintain your app’s forward-compatibility with the target version. If your app is set to target a particular API level, all features at that level that you use should be thoroughly tested to ensure your application works.

With the requirement for new apps to have the target API level of 26, there is at least one feature that is yet to be catered for in Delphi apps. In Android 6 (API level 23), requirements were introduced regarding application users privacy; namely that some resources are now designated “dangerous”, and applications will need to explicitly request permission to access to these resources at runtime, as well as having the permissions listed in the manifest.

In order to handle requests for permissions correctly, the application should call the requestPermissions method (of the Activity class), and the activity must override the onRequestPermissionsResult method of the Activity class in order to determine whether the user granted or denied access. Unfortunately at present, this method is not implemented by the FMXNativeActivity class (part of the FMX Java runtime), so unless you’re keen on modifying the FMX Java source and recompiling it, a little bit of “hacking” is required.

Fortunately, I (with the invaluable assistance of Brian Long) have done the work for you. Rather than go into an in-depth discussion of how the solution was devised, I’ll field questions in the comments. I will however describe how the demo is put together, which should guide you in how you can integrate it into your own apps.

First, you will need to download the Android SDK for API level 26 (i.e. Android 8). Run SDKManager located in the Android SDK folder, which by default appears in the directory as per the following image. Select at least the Android 8 SDK Platform and click Install:

Use the SDK Manager to change the illustrated SDK settings to API 26 (in my case 26.0.2):

Once you have created your project, the manifest template needs to be modified to replace the FMXNativeActivity with a class that descends from that class, but implements onRequestPermissionsResult. The following shows the part that needs to be changed:

NOTE: A side-effect of replacing the activity is that when debugging via the IDE, when the IDE says “Launching”, you will need to start the application manually on the device. 

Next, the application needs to have the .jar file that contains the replacement activity added to the Android Libraries node, which is under the Android target in the Project Manager:

The file (dw-nativeactivity.jar) is in the Lib folder of the KastriFree project.

I’ve devised a class called TSystemHelper, which follows a similar pattern to other classes I’ve constructed to implement functionality in a cross-platform manner. In the main form of the demo, I create an instance of TSystemHelper, assign the OnPermissionsResult event to a handler, and call the RequestPermissions method for various permissions that are classified as “dangerous”, such as Camera, Location and SMS.

The permission request results are passed back in a dynamic array of records (of type TPermissionsResults). The type has a  helper class that adds convenience methods such as AreAllGranted, which indicates whether all permissions in the request were granted. In my tests on my Android 8 device, I was expecting the user to be able to choose which permissions to grant or deny, however it just prompted me with a single query. Perhaps this is different (or will be) on other versions of Android, so I’ve left the code as it is.

You can find the code for the demo in the KastriFree project (the demo relies on units contained in the project).

Targeting Android 8 and higher, continued

$
0
0

In my last article, I discussed the new requirements for Android apps on Google Play to target Android 8 (API level 26) and higher, and described one possible way of handling permission requests at runtime, as required when targeting Android 6 (API level 23) and higher. In this article, I describe an alternative, and also cover the requirement for apps targeting Android 7.0 (API level 24) and higher when accessing “external” URIs.

As per the last article, this article and demo was produced using Delphi Tokyo 10.2.3. The code may or may not work with earlier versions.

UPDATE (July 20th, 2018): The demo has been modified to allow for a workaround for local notifications when targeting API 26 or greater. See below for details.

TL;DR: The demo code for this article can be found in the KastriFree project, here.

Targeting an API level

There has been some confusion over what “targeting” a particular API level means. It does not mean which Android SDK you have configured in the SDK Manager in the Delphi IDE. It means what value is set for the targetSdkVersion value in the application manifest:

This is the API level at which Android will act in “compatibility mode” for. By default, Delphi sets this value to 14 (Android 4.0). The device can still have a later version of Android installed, and your application can still use API calls that apply for the installed version, however it will act as if it were at the target level. For example, an app targeting API level 14 on a device running Android 6 will not require requesting permissions at runtime.

At present, to target a particular API level, you need to manually modify the AndroidManifest.template.xml file, as described in the previous article.

A change in the method for responding to runtime permission requests

In the previous article, I devised a method of requesting permissions at runtime that required “overriding” FMXNativeActivity in the manifest. Unfortunately, that meant having to launch the application from the device manually in order to debug via the IDE. Thanks to a suggestion from Stephane Vanderclock, author of the Alcinoe library, I’ve devised a different method that removes the need to “override” the activity.

Through necessity (which will become clearer later), I have created a new class called TPermissionsRequester, which more accurately reflects what it does anyway. This replaces the TSystemHelper class, which I recommend you stop using altogether, if you choose to change to using this new solution.

When a runtime permissions request is made, the focus moves away from the application, and once the user has granted or denied permission, the application receives focus again, and the BecameActive event fires. TPermissionsRequester handles this event, and if a permissions request has been made, checks whether such permissions were granted:

procedure TPlatformPermissionsRequester.ApplicationEventMessageHandler(const Sender: TObject; const AMsg: TMessage);
begin
  case TApplicationEventMessage(AMsg).Value.Event of
    TApplicationEvent.BecameActive:
      CheckPermissionsResults;
  end;
end;

procedure TPlatformPermissionsRequester.CheckPermissionsResults;
var
  LResults: TPermissionResults;
  LIndex, I: Integer;
begin
  if FRequestCode = -1 then
    Exit;
  SetLength(LResults, Length(FPermissions));
  for I := Low(FPermissions) to High(FPermissions) do
  begin
    LIndex := I - Low(FPermissions);
    LResults[LIndex].Permission := FPermissions[I];
    LResults[LIndex].Granted := TOSDevice.CheckPermission(FPermissions[I]);
  end;
  TOpenPermissionsRequester(PermissionsRequester).DoPermissionsResult(FRequestCode, LResults);
  FRequestCode := -1;
end;

So all the developer needs to be concerned with is requesting the actual permissions, and handling the OnPermissionsResult event:

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  FRequester := TPermissionsRequester.Create;
  FRequester.OnPermissionsResult := PermissionsResultHandler;
end;

procedure TForm1.TakePhotoButtonClick(Sender: TObject);
begin
  FRequester.RequestPermissions([cPermissionReadExternalStorage, cPermissionWriteExternalStorage, cPermissionCamera], cPermissionsCodeExternalStorage);
end;

As TPlatformPermissionsRequester (in DW.PermissionsRequester.Android) uses TApplicationEventMessage which comes from the FMX.Platform unit, it cannot be used in a service (at least at present). That’s why TPermissonsRequester was created, and CheckPermission method has been moved to TOSDevice, so that it can be used in a service.

Note that in this demo, permissions are requested only when absolutely required (i.e. when the user wants to take a photo). This makes the application a little more user friendly than if the permissions were requested as soon as the application starts.

In the code, I am passing the string representation of the permission, e.g.

cPermissionCamera = android.permission.CAMERA;

The format for all of the “dangerous” permissions is the same, i.e. it follows the pattern: android.permission.XXXX, where XXXX is one of the “Permissions” values in this list, so you can define your own values to be passed to the RequestPermissions method in the same manner.

Note that even though you’re requesting permissions at runtime, you still need to check the checkboxes for those permissions in the Uses Permissions section of the Project Options

Accessing “external” URIs

If you have tried to target API 26, and attempted to use the TTakePhotoFromCameraAction, you would have noticed that it fails, with the error:

android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()

From Android 7.0 (API level 24), when accessing “external” URIs, it is necessary to use the FileProvider class. Unfortunately, the code that causes the problem for TTakePhotoFromCameraAction is actually within the Java binaries (FMX.jar) that Delphi uses, so rather than expect developers to patch and recompile FMX.jar, I’ve come up with an alternative solution.

TMediaLibrary is an alternative for (at least for the present), TTakePhotoFromCameraAction. I may or may not expand on it, depending on how useful it becomes, and how Embarcadero handle the required changes. It has a single method: TakePhoto, and has event handlers for when the image is captured successfully, or when the user canceled.

  TMediaLibrary = class(TObject)
  private
    FPlatformMediaLibrary: TCustomPlatformMediaLibrary;
    FOnCanceled: TNotifyEvent;
    FOnReceivedImage: TReceivedImageEvent;
  protected
    procedure DoCanceled;
    procedure DoReceivedImage(const AImagePath: string; const ABitmap: TBitmap);
  public
    constructor Create;
    destructor Destroy; override;
    procedure TakePhoto;
    property OnCanceled: TNotifyEvent read FOnCanceled write FOnCanceled;
    property OnReceivedImage: TReceivedImageEvent read FOnReceivedImage write FOnReceivedImage;
  end;

Also it is currently useful only on Android, as there are no implementations for the other platforms.

TMediaLibrary makes use of a function from the newly created unit DW.Android.Helpers, called UriFromFile. This method checks what the targetSdkVersion value is, and if >= 24 it uses the FileProvider class to create a Uri from the file reference, passing in the “authority” which needs to be specified in the manifest. This is done by adding a Provider section, like this:

When using the UriFromFile function, the value specified in the manifest for android:authorities will need to match the application’s package name. This value appears in the Project Options under Version Info.

Note the entry: <meta-data android:name=”android.support.FILE_PROVIDER_PATHS” android:resource=”@xml/provider_paths”/>. This is a reference to a file that needs to be deployed with the application, called (in this case): provider_paths.xml. This is what the file looks like:

This file needs to be added to the application’s deployment using Deployment Manager, and the Remote Path value set to: res\xml\

Remember that when targeting API level 24 or greater, your app will need to request the appropriate permissions at runtime (as per the demo), before attempting to use the TakePhoto method. I had considered making TMediaLibrary handle all this automatically; perhaps in the future 🙂 You should also remember that there are a number of other “dangerous” permissions that may be used throughout Delphi (e.g. by TLocationSensor). Be sure to request permissions at runtime for any of those that require it, before you attempt to use them.

Remember also that the UriFromFile function can be used in your own code if your app needs to access “external” files. You will certainly know this is the case if your application throws the dreaded FileUriExposedException.

Taking care of the status bar

In the previous article, it had slipped past me that when changing the API target, the status bar was no longer visible!

In this demo, the workaround was to change the Fill property of the form, setting the Color to Null, and the Kind to Solid. In addition, a Rectangle is added to the form, the Align property set to Contents, and the Color property of Fill set to Whitesmoke. Now the status bar is visible again, however remember that this is just a workaround; hopefully an official solution will present itself in the next update.

Getting notified..

The demo has been updated (July 20th, 2018) to allow for a workaround for local notifications. In order for them to work however, you will need to patch the System.Android.Notification unit from the Delphi source. Details on how to do this are in the readme. Also, you will need to use new files added to KastriFree, namely:

DW.Androidapi.JNI.App
DW.Androidapi.JNI.Support
DW.NotificationReceiver
support-compat-26.1.0.jar

Note also in the demo that the original android-support-v4.dex.jar is disabled. This is because the demo uses a newer version of the Android support libraries:

Are there other issues that need to be accounted for when targeting later API levels?

I’m glad you asked! At present, I’m unaware of any other issues, however my plan is to expand the demo in the future if any others come to light. If you are aware of any that are not related to what I’ve already covered, please let me know in the comments.

The demo code for this article can be found in the KastriFree project, here.

Viewing all 58 articles
Browse latest View live


Latest Images