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

Adding Firebase Cloud Messaging to your mobile apps – part 1

$
0
0

Firebase Services from Google are becoming very popular, and Firebase Cloud Messaging (FCM) is just one part of it. In this article I show how you can incorporate support for FCM in your Delphi apps for Android and iOS.

Firstly, some acknowledgements: Many thanks to Stephane Vanderclock (aka Loki) from whom I gleaned much information, for the countless late nights he spent tearing his hair out working out how to make this all work, and for the code he wrote (which you can find here) which I’ve based almost all of my work on. Thanks also to Steven Chesser who was a willing guinea pig, and who is also thankful to Stephane and myself for probably saving his job, and thanks to Allen Drennan and company from Grijjy, for their amazingly excellent blog articles (particularly this one, from which this article is partly based) and their tools (one of which is used for this article).

Secondly, a few caveats: This article and the accompanying code is close to the bare minimum for incorporating Firebase Cloud Messaging. Stephane and I are not exactly experts in Firebase Cloud Messaging, and this implementation doesn’t pretend to cover everything. The Firebase libraries contain a number of other services (such as Firebase Crash, Analytics etc) that are not used by the code presented here: a future article may include how to use these. Also, the message payload that is passed to the OnMessageReceived event is completely raw: it is left up to the reader as to how the data might be dealt with when a notification is received while the app is running. Most importantly however, is that (as per step 9) this implementation requires that you disable all of the Google Play Services that Delphi adds automatically, so if your app relies on any of these, they will not work.

Thirdly, because this article grew a lot after I started it, I’ve decided to split it into two. This first part, although there are some pointers related to iOS, will feature Android, and iOS will follow shortly in part 2.

Fourthly, this article heralds the release of the free version of the Delphi Worlds “Kastri” library, which includes the framework for Firebase Cloud Messaging support, and the starter project which you will find in the demos folder. More features and demos will be added to this free library in the future, and the commercial version of the library will be released in the next few weeks.

Last of all: I used Delphi 10.2 Tokyo for this project, however it also works with at least Delphi 10.1 Berlin.

Let’s start!

1. Creating a project in Firebase Console:

If you’ve never used Firebase before, the first thing you’ll want to do is to create a project in the Firebase console. Google provide some how to’s on YouTube for Firebase, so you might want to check them out. I recommend watching the one for iOS first, and pay attention up to the 2:23 mark, as the rest is specific to Xcode. Next, watch the video for Android, paying attention to the following sections: 0:59 – 1:14, 1:43 – 2:44, 2:59 – 3:03 and 3:46 – 4:04

If you already have a project set up for iOS and Android, you’ll need to download the files (if you haven’t done so already) that have information about your app, for the specific platforms. The one for iOS is GoogleService-info.plist. Please refer to the following images as to where in Firebase console you’ll need to download it from:

 

The file for Android is google-services.json:

Later you’ll be using these files in your project.

2. Download the required packages using the Android SDK Manager

The Android version of Firebase requires two packages that can be downloaded using the Android SDK Manager, which can be found at the root of the Android SDK installed on your system. If you opted to install the Android SDK with Delphi, it will be located in the folder:

C:\Users\Public\Documents\Embarcadero\Studio\xx.0\PlatformSDKs\android-sdk-windows

Where xx refers to the version of BDS on your system. Run SDKManager.exe, and scroll down to the Extras section in the list of packages. There you will see Android Support Repository and Google Repository:

Select these two packages, and click Install, then accept the license agreements and the packages will install. These packages contain files in .aar format, which are really just .zip files.

In order to make it easier to extract the required files from these packages, I have developed a tool called ACTED, which stands for Android Companion Tool Enhancing Delphi, and contains functions that automate extraction of files, create required jar files, and transform your google-services.json file into a resource required by Android. ACTED can be downloaded from here. For this demo, you will also need the configuration file (firebase-aars.json) from the starter project, to be used by ACTED when extracting the .aar files. If you’ve read the articles at Grijjy, this process may be familiar to you, however ACTED wraps everything up into two easy steps and removes the need for merging .dex files, which is done by Delphi at build time anyway.

3. Download the starter project:

As indicated in the introduction, there is a starter project that is contained in the demos of the Kastri Free library, which you will need to download (use the green “Clone or Download” button) if you wish to use the starter project. The companion jar file (dw-firebase.jar) in the Lib folder contains code for the service portion of the application.

The first thing you should do is modify the package name value in the project options, to match the identifier for your  project in Firebase console, e.g:

4. Extracting the resources from the .aar packages:

Note: As per the Grijjy article, you will need to select JDK 1.7 in order for this to work. If you do not have JDK 1.7 installed, you can download it from here.

Once you have the required JDK 1.7, start the ACTED tool, click the Options button, and select the appropriate options, and click OK e.g:

Next, click the “Extract AAR Files” button, click the Load button, navigate to the firebase-aars.json file in the Configuration folder of the Kastri Free library, and click OK. This should load the list box with all required packages, and automatically select which packages that need their res files extracted from them:

Click the ellipsis button for the Extract Path edit, and select a folder for the extracted files. I recommend that you choose a folder which will be common to apps that require Firebase services, however you can choose a subfolder of your project if you desire.

Click the “Extract” button, and ACTED will extract all necessary files, and merge the xml files (where necessary) that contain resources.

5. Creating the jar containing the R classes:

In ACTED, click the “Create R Jar” button, then click the ellipsis button for the Path to libraries and resources edit, and select the folder which you specified in the previous step. Click the ellipsis button for the R Jar Filename edit, and select a location for the jar that will contain the R classes. I recommend using the \lib folder under the folder which is in the “Path to libraries and resources” edit, and to name the jar play-services-r.jar, e.g:

Click the Create R Jar button, and ACTED will use the Java compiler to build the jar.

6. Creating the required strings.xml resource:

If you watched enough of the video of Firebase and Android, you may have noticed that in Android Studio, you can just add the google-services.json file to an Android Studio project, and you’re good to go. With Delphi, the google-services.json file will need to be transformed into strings.xml, which is deployed with the application resources. Start ACTED (if it isn’t running already), and click the “Create strings.xml from google-services.json” button, click the ellipsis button for where the resulting strings.xml will go. Since this is specific to an application, I recommend that this file go somewhere under the project folder. Click the Import button, and ACTED will prompt you for the location of your google-services.json file, e.g:

Click Open, and the strings.xml file will be created with all the required information.

7. Using DeployMan to deploy the resources:

The DeployMan tool from the Grijjy site is a great tool for deploying files in bulk, which is the case for this demonstration. NOTE: Before using this tool, make sure you close the project in Delphi, and make sure you have a backup of your project files. 

Close your project in Delphi, download and extract DeployMan, run it, and select the Android tab. Click File, Import .dproj and select your Delphi project. You will need to ensure that both the resources extracted in step 4, and your strings.xml, are included. Click the Add Folder button, select the res folder under the folder of the extracted resources, modify the target directory to be .\res, ensure you have the desired configurations selected (i.e. debug and/or release) and ensure that include subdirectories is checked. Click Add File, select your strings.xml file (generated in the previous step) and modify the target directory to be .\res\values. e.g.:

Click File, Save project

8. Modifying the application manifest:

The AndroidManifest.template.xml file in the project folder will need to be modified in order for Firebase Cloud Messaging to work. The manifest template in the starter project has already been modified, so you may use it as a reference for what changes need to be made. The changes are highlighted with xml comments that contain the text:

**** FCM ****

so that you know where they go. The rest of the manifest is what you would find in a typical manifest for a Delphi application for Android.

9. Managing the .jar files in the project:

The first part of this step is very important, and may affect you if your app relies on any of the Google Play Services that Delphi adds automatically. You must disable all but fmx.dex.jar and dw-firebase.jar in order for Firebase Cloud Messaging to work in your application.

In the Delphi project manager, expand the Target Platforms node, expand the Android node. Right-click each of the jars (except for fmx.dex.jar and dw-firebase.jar) and click Disable:

There are a number of other jar files required for this project, including those that were extracted in step 4. Right-click the Libraries node and click Add. Navigate to the lib folder under the extracted resources folder (from step 2) and select all the jar files, eg:

Click Open. If the jar containing the R classes that was built in step 5 is in a different folder, you will need to follow the same steps to add that, too.

10. Build, run  and test the project:

Before running the starter project, you should start the Monitor program that comes with the Android SDK. You’ll find a batch file called monitor.bat in the tools folder of the SDK root. For the default Android SDK install for Tokyo, monitor.bat is located in:

C:\Users\Public\Documents\Embarcadero\Studio\19.0\PlatformSDKs\android-sdk-windows\tools

It is wise to then create a filter, so you see only the messages for your application. To do this, click the green “+” button located in the LogCat tab, in the lower left, enter a name for the filter, and enter the package name for your application in the Application Name box:

Now go back to Delphi, select the debug configuration, compile, and run. Switch to the Monitor application, and watch for the messages when the application runs. The last message you should see is: “Token at startup:” followed by the token. You will need this value to send individual messages to the device, so select the message in Monitor, press Ctrl-C, the message will be copied to the clipboard, and you’ll be able to extract the token from it.

One convenient way of testing Firebase Cloud Messaging is to use the Hurl website. Go to the website, select the POST request type, and in the URL box, paste the following:

https://fcm.googleapis.com/fcm/send

The request will require 4 header values, like pictured here:

The Authorization header requires a name/value pair where the name is: key, and the value is the Server API key, which can be found in your project on Firebase console in the project settings, on the Cloud Messaging tab:

Due to an apparent limitation in Firebase Cloud Messaging, messages must have a “data” section, otherwise Firebase will not process the message on the device when the application is not running (i.e. the associated service for the application receives the message). This is an example message:

{
  "to": "<your-token-goes-here>",
  "data": {
   "notification_title": "Firebase Cloud Messaging rules!",
   "notification_largeicon": "https://avatars3.githubusercontent.com/u/22670829?v=3&s=460",
   "notification_text": "Congratulations, you received a message",
   "notification_vibrate": "1",
   "notification_visibility": "1",
   "notification_priority": "2",
   "notification_onlyalertonce": "1"
   }
}

As per the example, in the “to” value, supply the device token that was copied earlier. On the Hurl page, click Add Body, and paste the message into the Parameters box.

Click “Launch Request”, and all things being well you should receive a 200 OK response, and the notification should appear in your app! Next, terminate the app on the device so that you know it is not running, and send another message. Now press the power button on your device so that the display turns off (don’t turn it off completely!), and send another message. You should see the notification appear in the lock screen.

Now for an explanation of the “non-obvious” items in the message:

notification_largeicon can be a url to an image somewhere on the internet. If you use this value, make sure the image is relatively small so that it doesn’t use too much data.
notification_vibrate is either 0 or 1, and a value of 1 combined with a notification_priority of 2 ensures that the message also appears in the “home” screen.
notification_onlyalertonce is either 0 or 1, and a value of 1 means that the user isn’t repeatedly bothered by the notification

With this implementation, you can include whatever other data you wish as long as it is a valid json pair, and it will be passed to the application if it is already running, or when the user launches it from the notification.

Again, please bear in mind that I am no expert in Firebase Cloud Messaging; Stephane and myself have only just managed to have come this far just recently, and we’re still making refinements to the process. We hope however, that this article can give you a huge “leg up”.  Sometime in the next few days, Part 2 will be published, outlining the requirements for making this work on iOS.


Adding Firebase Cloud Messaging to your mobile apps – part 2

$
0
0

This is Part 2 of a 2-part article on incorporating support for FCM in your Delphi apps for Android and iOS, and this part covers implementation on iOS.

UPDATE 3: A change in the Firebase SDK (from version 4) has meant that the demo will not compile without a couple of changes, namely to include the FirebaseNanoPB framework in the search paths, and a corresponding change to the DW.iOSapi.Firebase unit. See step 2 for the details regarding the search path, and go to the source to download the changed unit.

UPDATE 2: Thanks to Steven Chesser who found this issue, and to Dmitry Arefiev for coming up with a solution:

If you’re using FireDAC with SQLite in your app, you’ll likely find that the app crashes on startup. It appears that FireDAC binds to SQLite static libraries that are incompatible with FCM. To alleviate this issue, please follow these steps:

  1. Make a copy of FireDAC.inc from the C:\Program Files (x86)\Embarcadero\Studio\xx.0\source\data\FireDAC folder, and put it in your project folder (where xx is the number for your Delphi install e.g. 18 for Delphi 10.1 Berlin)
  2. Modify FireDAC.inc to change the line:
    {.$UNDEF FireDAC_SQLITE_STATIC}
    Remove the “.” after the “{” so that it becomes:
    {$UNDEF FireDAC_SQLITE_STATIC}
  3. In Project Options, select: All configurations – iOS Device- 64 bit platform, and add the following to the search path:
    $(BDS)\source\data\FireDAC
  4. Clean and Build the project (as opposed to Compile), run it, and all should be OK

UPDATE 1: Thanks again to Stephane Vanderclock, notifications on iOS will now appear in the notification center, even when the app is in the foreground! To take advantage of this, check out the latest version of the demo from the Kastri Free project.

If you haven’t done so already, please read at least step 1 of Part 1, because it has important information about how to retrieve the .plist file that’s required for your iOS app, and about the starter project.

This article assumes that you have a certificate, app id and provisioning profile set up already, and added to your Mac. If you do not, please refer to this article.

Let’s start!

1. Download the Firebase SDK from the Firebase site

Go to the Firebase iOS setup page, navigate to the “Integrate Without Cocoa Pods” section (since we’re not using Cocoa Pods) and click the link in step 1 to download the SDK. Extract it somewhere appropriate that’s common to your applications. Inside, you’ll notice a a bunch of folders, and inside each folder is one or more “framework” folders (they have an extension of “.framework”). Inside each of those, in the root, is a file with the same name, with no extension. These are the binaries that your app needs in order to use the Firebase SDK.

2. Add paths to the framework files in your Delphi project

The easiest way to make it so that the compiler can find the files is to add a path for each of the necessary frameworks. In the Project Options for your Delphi project, select Delphi Compiler in the tree, and select All configurations – iOS Device – 64 bit platform in the Target combo. In the Search Path option, you’ll need to add a path for each of the following (where <firebase> is the root of the Firebase SDK that you extracted):

<firebase>\AdMob\GoogleMobileAds.framework\
<firebase>\Analytics\FirebaseCore.framework\
<firebase>\Analytics\FirebaseAnalytics.framework\
<firebase>\Analytics\FirebaseInstanceID.framework\
<firebase>\Analytics\GoogleToolboxForMac.framework\
<firebase>\Messaging\FirebaseMessaging.framework\
<firebase>\Messaging\Protobuf.framework\
<firebase>\RemoteConfig\FirebaseRemoteConfig.framework\
<firebase>\Analytics\FirebaseNanoPB.framework\     (this is a recent addition, to support Firebase SDK 4, and does not appear in the image below)

e.g:

While we’re in the Project Options, you’ll also need to add a parameter to the Linker options. Under Delphi Compiler, select Linking, and add -ObjC to the “Options passed to the LD linker” option (as per part 3 of the SDK requirements for apps not using Cocoa Pods on the Firebase iOS setup page):

You’ll need to repeat the above for iOS Device – 32 bit platform when you’re ready to deploy to the App Store or Test Flight, unless Apple has removed the 32-bit requirement by then.

Go to the Version Info section of the Project Options and modify the CFBundleIdentifier field so that it matches the identifier for your App ID on the Apple Developer site:

Then go to the Provisioning section, just to make sure the certificate and provisioning profiles have been found, and click OK.

3. Add the required frameworks from the iOS SDK to the Delphi SDK Manager

If you’re not already familiar with how to add other frameworks to the iOS SDK, you can refer to this article. You’ll need to add each of the following frameworks:

AudioToolbox
CoreAudio
CoreMIDI
MediaToolbox
Metal
SystemConfiguration
UserNotifications

Note that the case of the name is very important! (eg it’s CoreMIDI, not CoreMidi). Remember to click Update Local File Cache when you are done adding those, and remember to click OK.

4. Add GoogleService-info.plist to the deployment

In Delphi, click Project, Deployment and select All configurations – iOS Device – 64 bit platform in the combo. Click the button with the white rectangle and green “+” on it to add, then select your GoogleService-info.plist file and click Open, e.g:

Now: would you believe that is it? Other than compiling and running your project, that is. While Monitor is good for watching log messages on Android, on iOS I find iOSConsole to be a decent equivalent on iOS, and it doesn’t require you to start Xcode. If you download that (onto your Mac), install and run it, you’ll be able to see the log messages for the starter project. Put the text “FCMStarter” (without the quotes) in the filter box of iOSConsole.

Run the app, and the first time you do, you’ll be prompted as to whether you would like the app to receive notifications. Be sure to tap “Allow”! You should also see the token appear in the upper memo. The token will also appear in the iOSConsole window:

Just like in Android’s Monitor app, you can copy the lines in iOSConsole, and extract the token from the copied text.

For iOS, FCM messages need to be of the notification format, e.g:

{
  "to": "&amp;amp;lt;yourtoken&amp;amp;gt;"
    "notification" : {
    "body" : "Congratulations, you received a message",
    "title" : "Firebase Cloud Messaging rules!",
    "badge": "0"
    }
}

Where <yourtoken> is the one you retrieved from the log.

Please refer to the last step of the previous article where I mention how to use Hurl to send test messages. If you’re using the starter project, you should check that notifications appear in the memo on the main form when the app is running, and for all apps using this framework, notifications will appear in the notification center when the app is in the background, or not running at all.

That’s all on this subject for the moment. There may be follow-up articles as to how to go about using the SubscribeToTopic and UnsubscribeFromTopic methods, and further developments going forward.

Making cross-platform apps with Delphi is easy

$
0
0

Developing software with Delphi is easy regardless of “cross-platformness”, relatively speaking, in comparison to other tools I’ve used, however this article shows how insanely easy it can be to put together an application that runs on iOS, Android, Windows and MacOS.

UPDATE: Developer Jerry Dodge has alerted me to this site of his, called JD Weather, that supports a whole bunch of weather service using the same API! You should be able to easily modify the demo here to use it.

Just over a week ago (in May, 2017), Jim McKeeth and I were talking about Xamarin, and how it can be a challenge to develop cross-platform apps with it. He was referring specifically to this article from Tim Anderson, how a programming challenge from Paul Thurrott showed a few bumps in Microsoft’s path to cross-platform mobile. (The challenge has since been concluded).

Jim asked me if I’d be interested in writing an article about how easy it can be to develop a cross-platform weather app. There’s already a plethora of apps out there; this was just an exercise it how easily it can be done with Delphi.

Jim supplied me with a link to an API, namely this one from OpenWeatherMap. He also pointed me to some free clipart on Pixabay, however apart from that, I was all on my own.

The result is the demo app that you can download from here. It showcases some of the technologies available in Delphi that you can use to easily build a cross-platform weather app, namely:

  • FMX controls
  • Json Parsing support from the REST.Json unit
  • Cross-platform HTTP communications using THTTPClient
  • Location Services, using TLocationSensor

The first task was to examine the API, and see what I’d need to do in order to communicate with it. In order to use any of the OpenWeatherMap APIs, you need an API key. Fortunately, they have one that you can use for free (with usage limitations), and you can apply for one here.

The OpenWeatherMap APIs have different payload types, the default of which is JSON; the other two are XML and HTML. I chose JSON, because I knew I could quickly build some classes that I could easily populate using parsing from the REST.Json unit.

The JSON version of the API call produces a payload like this:

{
  "coord": 
  {
    "lon": 138.58,
    "lat": -34.89
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "clearsky",
      "icon": "01d"
    }
  ],
  "base": "stations",
  "main": 
  {
    "temp": 292.15,
    "pressure": 1020,
    "humidity": 42,
    "temp_min": 292.15,
    "temp_max": 292.15
  },
  "visibility": 10000,
  "wind": 
  {
    "speed": 5.7,
    "deg": 240
  },
  "clouds": 
  {
    "all": 0
  },
  "dt": 1494738000,
  "sys": 
  {
    "type": 1,
    "id": 8204,
    "message": 0.0044,
    "country": "AU",
    "sunrise": 1494711125,
    "sunset": 1494748298
  },
  "id": 2062944,
  "name": "Prospect",
  "cod": 200
}

Instead of just sticking all the code in the main form unit (which might be the case for much simpler demos), I decided I’d split up the functionality into distinct parts:

  • OW.Data contains the classes used for receiving the JSON data
  • OW.Consts contains the constants used by the OW.API unit
  • OW.API has the TOpenWeatherAPI class that does the work of making the API call, and returning the data

You’ll notice I named the class that represents the JSON data: TOpenWeatherByCoords. I named it this way in case data structures for other types of calls are different. I’ve left it as an exercise for the reader to determine whether they are. You may also note that the TOpenWeatherRain class has the private field exposed as public in a property called: vol3h. This is because it’s not possible to have an identifier in Delphi that starts with a number (it would otherwise been 3h).

The code for the TOpenWeatherAPI class is fairly simple. The GetByCoords method simply formats the URL, and fires off a task:

procedure TOpenWeatherAPI.GetByCoords(const ALatitude, ALongitude: Double);
var
 LQuery: string;
begin
  LQuery := Format(cOpenWeatherByCoordsQuery, [cOpenWeatherAPIKey, ALatitude, ALongitude]);
  TTask.Run(
    procedure
    begin
      DoSendRequest(cOpenWeatherByCoordsURL + LQuery, TOpenWeatherRequest.ByCoords);
    end
  );
end;

that creates a THTTPClient, calls Get on the URL:

procedure TOpenWeatherAPI.DoSendRequest(const AURL: string; const ARequest: TOpenWeatherRequest);
var
  LHTTP: THTTPClient;
  LResponse: IHTTPResponse;
begin
  LHTTP := THTTPClient.Create;
  try
    LResponse := LHTTP.Get(AURL);
    if LResponse.StatusCode = cHTTPResultOK then
      DoProcessContent(LResponse.ContentAsString, ARequest)
    else ; // Left as an exercise to the reader
  finally
    LHTTP.Free;
  end;
end;

and processes the result. DoProcessContentByCoords uses TJson to parse the result and send the resulting JSON object to the OnByCoords event, of course synchronised with the main thread:

procedure TOpenWeatherAPI.DoProcessContentByCoords(const AContent: string);
var
  LByCoords: TOpenWeatherByCoords;
begin
  try
    LByCoords := TJson.JsonToObject&amp;amp;amp;amp;amp;amp;amp;amp;lt;TOpenWeatherByCoords&amp;amp;amp;amp;amp;amp;amp;amp;gt;(AContent);
    TThread.Synchronize(nil,
      procedure
      begin
        DoByCoords(LByCoords);
      end
    );
  except
    // Left as an exercise to the reader
  end;
end;

In the main form, I have an instance of TOpenWeatherAPI and a handler for OnByCoords, called APIByCoordsHandler. In that method is the code for taking the data from the JSON object and populating the controls on the form:

procedure TfrmMain.APIByCoordsHandler(Sender: TObject; const AByCoords: TOpenWeatherByCoords);
var
  LWeather: TOpenWeatherWeatherItem;
begin
  LocationLabel.Text := AByCoords.name;
  TemperatureLabel.Text := GetTemperatureText(AByCoords.main.temp);
  if Length(AByCoords.weather) &gt; 0 then
  begin
    LWeather := AByCoords.weather[0];
    WeatherImage.Bitmap.LoadFromURL(cOpenWeatherWeatherImagesURL + LWeather.icon + '.png');
    WeatherLargeImage.Bitmap.LoadFromFile(TPath.Combine(FImagesPath, LWeather.icon.Substring(0, 2) + '.png'));
    WeatherMainLabel.Text := LWeather.main;
  end;
  if AByCoords.rain &lt;&gt; nil then
    RainValueLabel.Text := Format('%.1f mm', [AByCoords.rain.vol3h])
  else
    RainValueLabel.Text := 'Nil';
  HumidityValueLabel.Text := Format('%.0f %', [AByCoords.main.humidity]);
  PressureValueLabel.Text := Format('%.1f hPa', [AByCoords.main.pressure]);
  WindSpeedValueLabel.Text := Format('%.1f km/h', [AByCoords.wind.speed]);
  WindDirectionValueLabel.Text := BearingToDirection(AByCoords.wind.degrees);
end;

You may notice a method on the Bitmap called LoadFromURL, which loads the image corresponding to the icon information passed in the JSON object. Where did LoadFromURL come from? The answer is in the OW.Graphics.Net.Helpers unit: I created a helper class that adds the LoadFromURL method to TBitmap, which uses a similar technique as the GetByCoords method of the TOpenWeatherAPI class. In this case, it sends off an asynchronous call to get the image from a URL, then synchronously calls LoadFromStream to load the image into the bitmap.

Back in the main form, the whole process is kicked off when the TLocationSensor receives a location:

procedure TfrmMain.LocationSensorLocationChanged(Sender: TObject; const OldLocation, NewLocation: TLocationCoord2D);
begin
  // Have location now, so turn off the sensor
  LocationSensor.Active := False;
  FAPI.GetByCoords(NewLocation.Latitude, NewLocation.Longitude);
end;

If you want the app to update when the user changes location, you’ll need to make sure the sensor is always on, however be aware of the usage limitations with the free API key.

Here’s a screenshot of the app running on Android:

It looks very similar on iOS, Windows and OSX, so I won’t bother putting up images of those; you can try it out for yourself! Remember that you if you use the OpenWeatherMap API, you will need to apply for an API key, which is free for limited use. Just replace the value for the cOpenWeatherAPIKey constant in OW.Consts with your API key value.

Some other exercises for the reader:

  • Show readings in units as chosen by the user, or by the users location, e.g. Temperature (conversion functions are supplied for this), rain volume, etc
  • Allow users to change the location without reference to the TLocationSensor
  • Use another API, or use more OpenWeatherMap APIs to retrieve more information
  • Make the UI a little better, including stretching of the background image

The trick here is that I built this project in only a few hours, and good part of that time was spent fiddling around with the UI (which I’m still not happy with). The best part is that from this one, single source project (take that, Xamarin!), I am able to deploy to iOS, Android, Windows and MacOS.

Enjoy!

Delphi Worlds Slack Team

$
0
0

Slack” is an internet collaboration tool that seems to be gaining popularity pretty quickly. I’ve been using it for over a year now, and have set up a team for Delphi Worlds.

I have channels dedicated to discussing the Kastri Free library, as well as other channels, and just discussing Delphi in general.

If you’d like to join, just visit this link, fill out the form and click “Request Invitation”

CodeRage XII is coming!

$
0
0

This is one CodeRage you won’t want to miss. Specifically, because there are some very interesting speakers this year, including Uncle Bob Martin (author of The Clean Coder, and many others) and Steve McConnell (author of Code Complete and Software Project Survival Guide), and some sessions will be live only (i.e. without replay).

CodeRage XII goes from November 7-9, 2017

Register for Code Rage XII now, at: https://community.embarcadero.com/article/16578-coderage

 

Supporting iPhone X display with Delphi

$
0
0

iPhone X has recently been released, and Delphi developers are a little in the dark about how their apps can support the new devices. This article goes some way to make a start in the right direction.

Update: I have released a Delphi 10.2 Tokyo version of the IDE expert I have been working on, which (among other things) automatically patches the .info.plist for you. It is located in the Kastri Free library. Please read the CodexInstall.txt file that accompanies it.

The code etc in this article has been tested with Delphi 10.1 Berlin and Tokyo 10.2.1, however it may be possible to make it work with earlier versions.

Delphi apps for iOS seem to work OK (so far) on iPhone X, however if you have seen a Delphi app (or other apps that are yet to support iPhone X display) on one of the devices, you’ll immediately notice that they take up as much of the screen as an app running on earlier devices. Take for example the FireUI App Preview app from Embarcadero:

Note how it doesn’t take up as much of the screen as this app I have been working on, which has had a few tweaks to make it look better on iPhone X:

There’s a number of changes that need to be made in order to make your apps look and work better on iPhone X than they otherwise would:

This article by Geoff Hackworth over at Medium goes some way to explain the changes in iPhone X. As per his article, iPhone X requires 2 new launch screen images, so they need to be added to your app deployment. In addition, the .info.plist file that Delphi generates will need to be customised because (at the time of writing) the IDE is yet to be equipped to handle iPhone X launch images.

You’ll need to deploy the app at least once in order for the <projectname>.info.plist file to be created. Once that is done, open the file in your favourite editor, navigate to the UILaunchImages key, and add the following two entries inside the array node:

  <dict>
    <key>UILaunchImageSize</key>
    <string>{375, 812}</string>
    <key>UILaunchImageName</key>
    <string>Default-812h</string>
    <key>UILaunchImageMinimumOSVersion</key>
    <string>8.0</string>
    <key>UILaunchImageOrientation</key>
    <string>Portrait</string>
  </dict>
  <dict>
    <key>UILaunchImageSize</key>
    <string>{812, 375}</string>
    <key>UILaunchImageName</key>
    <string>Default-Landscape-812h</string>
    <key>UILaunchImageMinimumOSVersion</key>
    <string>8.0</string>
    <key>UILaunchImageOrientation</key>
    <string>Landscape</string>
  </dict>

Save this file using a different file name (e.g. <projectname>.patched.info.plist), add it to the deployment, and disable the original <projectname>.info.plist:

Note that if you make any changes to the project options that affect the <projectname>.info.plist file, you’ll need to go through the re-patch process. I have an expert for the IDE in development that could make this process seamless.

Create your launch images (sized 1125×2436 pixels and 2436×1125 pixels), and add them to the project deployment. Modify the Remote Name properties of each entry in the deployment to coincide with the changes that were made in the .info.plist, e.g:

Now your app will able to make use of the extra space on iPhone X, however you’ll notice that this introduces a new problem. This is an example:

Notice how the app now invades the status bar/notch area, and goes below the horizontal stripe at the bottom. Depending on what is actually displayed at the bottom, that might be OK, however there will probably be occasions when you don’t want your app to invade that space, either. In landscape mode, the sides of the application can invade the “notch” area:

In order to “fix” this, I created a function that could detect whether the device is an iPhone X, which is in this unit in the KastriFree project, and used the function to make layouts visible that border the actual content of the application. e.g:

You may wish to use another method; whatever the case, you’ll need to account for the different layout in iPhone X.

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.

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

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.

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

Making cross-platform apps with Delphi is easy

$
0
0

Developing software with Delphi is easy regardless of “cross-platformness”, relatively speaking, in comparison to other tools I’ve used, however this article shows how insanely easy it can be to put together an application that runs on iOS, Android, Windows and MacOS.

UPDATE: Developer Jerry Dodge has alerted me to this project of his, called JD Weather, that supports a whole bunch of weather services using the same API! You should be able to easily modify the demo here to use it.

Just over a week ago (in May, 2017), Jim McKeeth and I were talking about Xamarin, and how it can be a challenge to develop cross-platform apps with it. He was referring specifically to this article from Tim Anderson, how a programming challenge from Paul Thurrott showed a few bumps in Microsoft’s path to cross-platform mobile. (The challenge has since been concluded).

Jim asked me if I’d be interested in writing an article about how easy it can be to develop a cross-platform weather app. There’s already a plethora of apps out there; this was just an exercise it how easily it can be done with Delphi.

Jim supplied me with a link to an API, namely this one from OpenWeatherMap. He also pointed me to some free clipart on Pixabay, however apart from that, I was all on my own.

The result is the demo app that you can download from here. It showcases some of the technologies available in Delphi that you can use to easily build a cross-platform weather app, namely:

  • FMX controls
  • Json Parsing support from the REST.Json unit
  • Cross-platform HTTP communications using THTTPClient
  • Location Services, using TLocationSensor

The first task was to examine the API, and see what I’d need to do in order to communicate with it. In order to use any of the OpenWeatherMap APIs, you need an API key. Fortunately, they have one that you can use for free (with usage limitations), and you can apply for one here.

The OpenWeatherMap APIs have different payload types, the default of which is JSON; the other two are XML and HTML. I chose JSON, because I knew I could quickly build some classes that I could easily populate using parsing from the REST.Json unit.

The JSON version of the API call produces a payload like this:

{
  "coord": 
  {
    "lon": 138.58,
    "lat": -34.89
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "clearsky",
      "icon": "01d"
    }
  ],
  "base": "stations",
  "main": 
  {
    "temp": 292.15,
    "pressure": 1020,
    "humidity": 42,
    "temp_min": 292.15,
    "temp_max": 292.15
  },
  "visibility": 10000,
  "wind": 
  {
    "speed": 5.7,
    "deg": 240
  },
  "clouds": 
  {
    "all": 0
  },
  "dt": 1494738000,
  "sys": 
  {
    "type": 1,
    "id": 8204,
    "message": 0.0044,
    "country": "AU",
    "sunrise": 1494711125,
    "sunset": 1494748298
  },
  "id": 2062944,
  "name": "Prospect",
  "cod": 200
}

Instead of just sticking all the code in the main form unit (which might be the case for much simpler demos), I decided I’d split up the functionality into distinct parts:

  • OW.Data contains the classes used for receiving the JSON data
  • OW.Consts contains the constants used by the OW.API unit
  • OW.API has the TOpenWeatherAPI class that does the work of making the API call, and returning the data

You’ll notice I named the class that represents the JSON data: TOpenWeatherByCoords. I named it this way in case data structures for other types of calls are different. I’ve left it as an exercise for the reader to determine whether they are. You may also note that the TOpenWeatherRain class has the private field exposed as public in a property called: vol3h. This is because it’s not possible to have an identifier in Delphi that starts with a number (it would otherwise been 3h).

The code for the TOpenWeatherAPI class is fairly simple. The GetByCoords method simply formats the URL, and fires off a task:

procedure TOpenWeatherAPI.GetByCoords(const ALatitude, ALongitude: Double);
var
 LQuery: string;
begin
  LQuery := Format(cOpenWeatherByCoordsQuery, [cOpenWeatherAPIKey, ALatitude, ALongitude]);
  TTask.Run(
    procedure
    begin
      DoSendRequest(cOpenWeatherByCoordsURL + LQuery, TOpenWeatherRequest.ByCoords);
    end
  );
end;

that creates a THTTPClient, calls Get on the URL:

procedure TOpenWeatherAPI.DoSendRequest(const AURL: string; const ARequest: TOpenWeatherRequest);
var
  LHTTP: THTTPClient;
  LResponse: IHTTPResponse;
begin
  LHTTP := THTTPClient.Create;
  try
    LResponse := LHTTP.Get(AURL);
    if LResponse.StatusCode = cHTTPResultOK then
      DoProcessContent(LResponse.ContentAsString, ARequest)
    else ; // Left as an exercise to the reader
  finally
    LHTTP.Free;
  end;
end;

and processes the result. DoProcessContentByCoords uses TJson to parse the result and send the resulting JSON object to the OnByCoords event, of course synchronised with the main thread:

procedure TOpenWeatherAPI.DoProcessContentByCoords(const AContent: string);
var
  LByCoords: TOpenWeatherByCoords;
begin
  try
    LByCoords := TJson.JsonToObject&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;TOpenWeatherByCoords&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;(AContent);
    TThread.Synchronize(nil,
      procedure
      begin
        DoByCoords(LByCoords);
      end
    );
  except
    // Left as an exercise to the reader
  end;
end;

In the main form, I have an instance of TOpenWeatherAPI and a handler for OnByCoords, called APIByCoordsHandler. In that method is the code for taking the data from the JSON object and populating the controls on the form:

procedure TfrmMain.APIByCoordsHandler(Sender: TObject; const AByCoords: TOpenWeatherByCoords);
var
  LWeather: TOpenWeatherWeatherItem;
begin
  LocationLabel.Text := AByCoords.name;
  TemperatureLabel.Text := GetTemperatureText(AByCoords.main.temp);
  if Length(AByCoords.weather) &amp;amp;gt; 0 then
  begin
    LWeather := AByCoords.weather[0];
    WeatherImage.Bitmap.LoadFromURL(cOpenWeatherWeatherImagesURL + LWeather.icon + '.png');
    WeatherLargeImage.Bitmap.LoadFromFile(TPath.Combine(FImagesPath, LWeather.icon.Substring(0, 2) + '.png'));
    WeatherMainLabel.Text := LWeather.main;
  end;
  if AByCoords.rain &amp;amp;lt;&amp;amp;gt; nil then
    RainValueLabel.Text := Format('%.1f mm', [AByCoords.rain.vol3h])
  else
    RainValueLabel.Text := 'Nil';
  HumidityValueLabel.Text := Format('%.0f %', [AByCoords.main.humidity]);
  PressureValueLabel.Text := Format('%.1f hPa', [AByCoords.main.pressure]);
  WindSpeedValueLabel.Text := Format('%.1f km/h', [AByCoords.wind.speed]);
  WindDirectionValueLabel.Text := BearingToDirection(AByCoords.wind.degrees);
end;

You may notice a method on the Bitmap called LoadFromURL, which loads the image corresponding to the icon information passed in the JSON object. Where did LoadFromURL come from? The answer is in the OW.Graphics.Net.Helpers unit: I created a helper class that adds the LoadFromURL method to TBitmap, which uses a similar technique as the GetByCoords method of the TOpenWeatherAPI class. In this case, it sends off an asynchronous call to get the image from a URL, then synchronously calls LoadFromStream to load the image into the bitmap.

Back in the main form, the whole process is kicked off when the TLocationSensor receives a location:

procedure TfrmMain.LocationSensorLocationChanged(Sender: TObject; const OldLocation, NewLocation: TLocationCoord2D);
begin
  // Have location now, so turn off the sensor
  LocationSensor.Active := False;
  FAPI.GetByCoords(NewLocation.Latitude, NewLocation.Longitude);
end;

If you want the app to update when the user changes location, you’ll need to make sure the sensor is always on, however be aware of the usage limitations with the free API key.

Here’s a screenshot of the app running on Android:

It looks very similar on iOS, Windows and OSX, so I won’t bother putting up images of those; you can try it out for yourself! Remember that you if you use the OpenWeatherMap API, you will need to apply for an API key, which is free for limited use. Just replace the value for the cOpenWeatherAPIKey constant in OW.Consts with your API key value.

Some other exercises for the reader:

  • Show readings in units as chosen by the user, or by the users location, e.g. Temperature (conversion functions are supplied for this), rain volume, etc
  • Allow users to change the location without reference to the TLocationSensor
  • Use another API, or use more OpenWeatherMap APIs to retrieve more information
  • Make the UI a little better, including stretching of the background image

The trick here is that I built this project in only a few hours, and good part of that time was spent fiddling around with the UI (which I’m still not happy with). The best part is that from this one, single source project (take that, Xamarin!), I am able to deploy to iOS, Android, Windows and MacOS.

Enjoy!

Delphi Worlds Slack Team

$
0
0

Slack” is an internet collaboration tool that seems to be gaining popularity pretty quickly. I’ve been using it for over a year now, and have set up a team for Delphi Worlds.

I have channels dedicated to discussing the Kastri Free library, as well as other channels, and just discussing Delphi in general.

If you’d like to join, just visit this link, fill out the form and click “Request Invitation”

CodeRage XII is coming!

$
0
0

This is one CodeRage you won’t want to miss. Specifically, because there are some very interesting speakers this year, including Uncle Bob Martin (author of The Clean Coder, and many others) and Steve McConnell (author of Code Complete and Software Project Survival Guide), and some sessions will be live only (i.e. without replay).

CodeRage XII goes from November 7-9, 2017

Register for Code Rage XII now, at: https://community.embarcadero.com/article/16578-coderage

 

Supporting iPhone X display with Delphi

$
0
0

iPhone X has recently been released, and Delphi developers are a little in the dark about how their apps can support the new devices. This article goes some way to make a start in the right direction.

Update 2: Delphi Tokyo 10.2.2 now supports the new layout on iPhone X, including launch images, so if you are using that version, almost all of what is described below does not apply. 10.2.2 accounts for the height of the notch area when in portrait mode, however you will still need to account for it when in landscape mode.

Update: I have released a Delphi 10.2.1 Tokyo version of the IDE expert I have been working on, which (among other things) automatically patches the .info.plist for you. It is located in the Kastri Free library. Please read the CodexInstall.txt file that accompanies it.

The code etc in this article has been tested with Delphi 10.1 Berlin and Tokyo 10.2.1, however it may be possible to make it work with earlier versions.

Delphi apps for iOS seem to work OK (so far) on iPhone X, however if you have seen a Delphi app (or other apps that are yet to support iPhone X display) on one of the devices, you’ll immediately notice that they take up as much of the screen as an app running on earlier devices. Take for example the FireUI App Preview app from Embarcadero:

Note how it doesn’t take up as much of the screen as this app I have been working on, which has had a few tweaks to make it look better on iPhone X:

There’s a number of changes that need to be made in order to make your apps look and work better on iPhone X than they otherwise would:

This article by Geoff Hackworth over at Medium goes some way to explain the changes in iPhone X. As per his article, iPhone X requires 2 new launch screen images, so they need to be added to your app deployment. In addition, the .info.plist file that Delphi generates will need to be customised because (at the time of writing) the IDE is yet to be equipped to handle iPhone X launch images.

You’ll need to deploy the app at least once in order for the <projectname>.info.plist file to be created. Once that is done, open the file in your favourite editor, navigate to the UILaunchImages key, and add the following two entries inside the array node:

  <dict></dict>
    <key>UILaunchImageSize</key>
    <string>{375, 812}</string>
    <key>UILaunchImageName</key>
    <string>Default-812h</string>
    <key>UILaunchImageMinimumOSVersion</key>
    <string>8.0</string>
    <key>UILaunchImageOrientation</key>
    <string>Portrait</string>
  </dict>
  <dict>
    <key>UILaunchImageSize</key>
    <string>{812, 375}</string>
    <key>UILaunchImageName</key>
    <string>Default-Landscape-812h</string>
    <key>UILaunchImageMinimumOSVersion</key>
    <string>8.0</string>
    <key>UILaunchImageOrientation</key>
    <string>Landscape</string>
  </dict>

Save this file using a different file name (e.g. <projectname>.patched.info.plist), add it to the deployment, and disable the original <projectname>.info.plist:

Note that if you make any changes to the project options that affect the <projectname>.info.plist file, you’ll need to go through the re-patch process. I have an expert for the IDE in development that could make this process seamless.

Create your launch images (sized 1125×2436 pixels and 2436×1125 pixels), and add them to the project deployment. Modify the Remote Name properties of each entry in the deployment to coincide with the changes that were made in the .info.plist, e.g:

Now your app will able to make use of the extra space on iPhone X, however you’ll notice that this introduces a new problem. This is an example:

Notice how the app now invades the status bar/notch area, and goes below the horizontal stripe at the bottom. Depending on what is actually displayed at the bottom, that might be OK, however there will probably be occasions when you don’t want your app to invade that space, either. In landscape mode, the sides of the application can invade the “notch” area:

In order to “fix” this, I created a function that could detect whether the device is an iPhone X, which is in this unit in the KastriFree project, and used the function to make layouts visible that border the actual content of the application. e.g:

You may wish to use another method; whatever the case, you’ll need to account for the different layout in iPhone X.

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

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.

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.

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

$
0
0

In this article I revisit (again) the seemingly perennial problem of controls being obscured by the virtual keyboard when it appears.

The code related to this article was tested with Delphi Tokyo 10.2.3, however it may work with earlier versions.

TL;DR: The demo code is part of, and relies on units from the KastriFree project.

Wind back the clock five years, and you’ll find yourself at the first article I wrote regarding this issue. The first revision of the article was two and a half years ago, however this time the solution is substantially different.

A “replacement” TVertScrollBox

In this solution, I’ve created a “replacement” descendant of TVertScrollBox, whereby the class has the same name. This means that if the unit is added to the uses clause after the appearance of FMX.Layouts, the form or frame will create an instance of the “replacement”, and so will have all the required behaviour.

In order for everything to work, the controls within the TVertScrollBox need to be contained within a control (e.g. a TLayout) that has its Align property set to Top:

This control is assigned to the ControlsLayout* property of the “replacement” TVertScrollBox, and when the Virtual Keyboard appears, the control is resized, and the viewport position of the TVertScrollBox is changed, if required.

(*It may well have been named something like ControlsContainer, because it does not need to be a layout; just a TControl descendant)

Taking care of TMemo

The viewport positioning is based on the position of the control, and its height, depending on the type of control. If it is a TCustomMemo descendant, the positioning is determined by the position of the caret. If the return key is pressed, the caret position will move further down, and the positioning is recalculated:

If you examine the code, you may note that the TIdleMessage is used to check when focus changes and caret position changes occur. This is because the Virtual Keyboard may remain visible (and therefore possibly no Virtual Keyboard “hide” or “show” message) when focus changes or caret position changes and position needs to be recalculated.

Orientation Changes

This solution also allows for when the orientation of the device is changed. On iOS, a change in orientation causes the Virtual Keyboard to disappear, however on Android, the Virtual Keyboard remains visible, so the new size of the Virtual Keyboard is obtained, and the positioning is recalculated.

Please consider this solution to be a work in progress, as I have not checked every single scenario, however it has become by far the easiest since its beginning as there is far less work for the developer to do.

Connectivity checker for mobile, revisited

$
0
0

Nearly 5 years ago I wrote an article for checking whether a mobile device has an internet connection. This article revisits that solution, dropping the “less-used” functionality, however it also gains an important feature.

The code and demo for this article was written and tested on Delphi Tokyo 10.2.3, however it should work on earlier versions. It relies on units found in the KastriFree project.

In the original article, the main function of the code was to test if the mobile device has an internet connection. This new solution does essentially the same, however it also has an event that is fired when the connectivity changes, i.e. if the internet connection is lost, or becomes connected.

The TConnectivity class has two class methods (i.e. you do not need an instance to call these methods), which are fairly self-explanatory:

IsConnectedToInternet
IsWifiInternetConnection

It also has an event handler called OnConnectivityChange, which is called when (as you may expect) the internet connectivity changes, passing a Boolean that indicates whether the connection was gained, or lost. On Android, this is achieved by using the MultiReceiver class from the KastriFree project, to receive broadcasts of connectivity changes.

There is also an important difference to the code from the original article regarding iOS: This solution no longer requires the libReachability library, thus resolving the compatibility issues people have been having recently. Many thanks to Horácio Filho for his help in making the Reachability APIs from the System Configuration framework work on Delphi. I may have been floundering for a while longer if it were not for his assistance.

Rather than go into a discussion of the code, I will field questions in the comments.

Firebase Cloud Messaging, revisited

$
0
0

A little over a year ago, I posted a two part article about integrating Firebase Cloud Messaging in your Delphi mobile apps. This time, it’s a single part article because much of the work is done for you, or is simplified.

UPDATE: I’ve had an enquiry about how to update that were built based on the previous articles, so I’ll address this in a follow-up article in the next couple of days

The code in this article was built and tested using an install of the latest Delphi 10.2.3 (build 25.0.31059.3231), and at least for iOS, will require specifically this version. For Android you may be able to make it work in Delphi 10.1 Berlin or earlier.

TL;DR: The demo can be found here, bearing in mind that it requires other parts of KastriFree, and requires you to do a little configuration (see the Configuration section in this article)

Firebase SDK changes

There have been quite a number of releases since the original article, which used version 10.2.1 of the Firebase Android SDK, and version 4.3.0 of the Firebase iOS SDK. This article uses the latest released SDKs: the Firebase Messaging Android library is numbered 17.1.0 (libraries that it depends on have lower numbers) and the Firebase iOS SDK is at 5.5.0.

In the previous articles, it was up to the reader to download and extract the SDKs using the tools described in the articles. This time, I have uploaded the bare minimum libraries to the KastriFree project so that the demo project should just compile and run, after supplying a couple of files of your own.

Firebase Cloud Messaging (FCM) support changes

Due to changes in the SDKs, the supporting code has also changed. This means that the older demo will probably not compile, and will certainly not work using the older SDKs.

Support for later versions of iOS and Android

Some may have noticed that notifications (both local and remote) are not working as expected in later versions of iOS. In this solution, the device will display a push notification when the app is not running, when the app is in the background, and when in the foreground. This is because it makes use of the later APIs, including UNUserNotificationCenter which allows notifications to show in the foreground.

Similarly, if on Android you are targeting API 26 or greater (which is a requirement for Google Play apps), you may have noticed that both local and remote notifications do not show at all. This is due to the API 26 requirement of the use of notification channels. The FCM support in this solution makes use of these, so your notifications will show. This solution also makes use of TPermissionsRequester introduced in an article regarding targeting API 26 or greater, so that the necessary “dangerous” permissions are handled.

Caveats

There is a known issue with linking to the Firebase iOS SDK when targeting some versions of the iOS SDK, namely iOS SDK 11.3 or later. It has also been reported that this is sometimes an issue with iOS 11.2 SDK, however it is not always the case. Hopefully this issue will be resolved in the next release of Delphi.

Configuration

As per the introduction above, much of the work this time has already been done for you. However there are two important aspects that you will need to take care of before the demo will work. In part 1 of the original article, once you have set up your Firebase Console project (for each platform you require), you will need to download google-services.json from the Android section, and GoogleService-info.plist from the iOS section.

  • For iOS:Save GoogleService-info.plist into the Resources folder of the project. You don’t need to add it to the Project Deployment because it has already been added.As per section 2 of the second article, you will need to modify the CFBundleIdentifier value:

    As per section 3, add the following frameworks to the iOS SDK:

    AdSupport
    AudioToolbox
    AVKit
    CoreAudio
    CoreMIDI
    ImageIO
    IOSurface
    MediaToolbox
    Metal
    SystemConfiguration
    UserNotifications

  • For Android:As per section 3 of the first article, you will need to modify the package name value:

    Create a copy of strings.template.xml from the KastriFree project, open it in an editor, then insert values for each of the entries that correspond to the values from google-services.json:

    and save the modified strings.template.xml as strings.xml, to the Resources folder of the demo.

    You won’t need to do this for the demo, however for other projects you will need to add the required jars, and disable conflicting jars, as per this image:

As per the intro, the demo is here, and remember that it relies on files from KastriFree.

Viewing all 80 articles
Browse latest View live