Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi Firemonkey iOS background processing

Problem Description:

I am currently developing and Android and iOS application with Delphi XE7 Firemonkey. This app needs to work offline so user can work with it and when the phone goes online the app sends all the work to the servers even if the app is on background or the phone is in stand by.

Android working solution:

As I found out, objects TThread and TTimer doesn't work as they stop running once the app goes to background or the phone goes to stand by.

I ended up finding this library for android that works like a TTimer object but it still works when the app goes to background or the phone is on stand by.

https://github.com/dkstar88/AsyncTask

Unfortunately it doesn't work on iOS as it behaves like TThread and TTimer and it stops once the app goes to background.

iOS tried solutions

I tried several approaches but i found that a lot of people has a lot of information about Android but a lot less for iOS.

The first thing i tried was adding in the plist file the required permissions to tell iOS that I want to do something in background. I tried the "fetch" property.

This alone doesn't work with TTimer, TThread or AsyncTask tried in Android.

So, I thought you have to tell iOS that you want to do something in background AND what procedure or code is you want iOS to execute when in backgroud.

The most promsinig code I found was in this links (view comments):

http://qc.embarcadero.com/wc/qcmain.aspx?d=128968

Here is the code I used, adapted from the previous link. It simply adds a line to a memo field for testing.

 unit uBackgroundiOS_2;

interface

uses iOSapi.UIKit,Macapi.ObjCRuntime,Macapi.ObjectiveC,iOSapi.CocoaTypes,FMX.Platform.iOS,iOSapi.Foundation,Macapi.Helpers,

FMX.Memo, system.SysUtils;

const UIBackgroundFetchResultNewData:NSUInteger = 0;
      UIBackgroundFetchResultNoData:NSUInteger = 1;
      UIBackgroundFetchResultFailed:NSUInteger = 2;

      UIApplicationBackgroundFetchIntervalMinimum:NSTimeInterval = 0;
      UIApplicationBackgroundFetchIntervalNever:NSTimeInterval = -1;

type
      id=Pointer;
      IMP = function( self : id; cmd : SEL; Param1 : NSUInteger ) : id; cdecl;

Var UIApp : UIApplication;
    memo: TMemo;

function imp_implementationWithBlock( block :Pointer ) : IMP; cdecl; external libobjc name  _PU + 'imp_implementationWithBlock';
function imp_removeBlock( anImp : IMP ) : integer; cdecl; external libobjc name _PU + 'imp_removeBlock';
function objc_addClass(Cls: Pointer): Integer; cdecl; external libobjc name _PU + 'objc_addClass';

procedure performFetchWithCompletionHandler(self:id; _cmd: SEL;application:id; handler:id);


procedure Init(p_memo: Tmemo);

implementation

procedure Init(p_memo: Tmemo);
var
  Res: Integer;
begin
{
Info:
use .\plist\BackgroundOPs.info.plist from Project Main Pfad and copy to Info.plist

include the Keys:
<key>UIBackgroundModes</key>
<array>
 <string>fetch</string>
 <string>remote-notification</string>
 <string>newsstand-content</string>
</array>
}
UIApp := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);

objc_msgSend((UIApp as ILocalObject).GetObjectId,sel_getUid('setMinimumBackgroundFetchInterval:'),UIApplicationBackgroundFetchIntervalMinimum);
Res := class_addMethod( objc_getClass('DelphiAppDelegate') , sel_getUid('application:performFetchWithCompletionHandler:'), @performFetchWithCompletionHandler,'v@:@?');

memo := p_memo;
end;



procedure performFetchWithCompletionHandler(self:id; _cmd: SEL;application:id; handler:id);
{
Using your device you can fire application:performFetchWithCompletionHandler with the following steps:
Put your app in the Background state.
Lock your device and wait 5 minutes. (I know, it's a waste of time, get some coffee)
Unlock your device, this is will fire the method.
}
var
  ahandlerimp: IMP;
begin
  try
    // here your code max. 30 Sec.
    Memo.Lines.Add(FormatDateTime('hh:nn:ss', Now));

    ahandlerimp := imp_implementationWithBlock(handler);
    ahandlerimp(self,_cmd,UIBackgroundFetchResultNewData); // return the Do- code
    imp_removeBlock(ahandlerimp);
  except
  end;
end;

end.

This doesn't work in my iphone 4 with iOS 8.4.1. I left the phone in stand by for a while and when I checked the app the memo field was in blank.

Any idea of what could be wrong? I am a bit of a dead end here.

Thanks a lot!

PS: in my original post i put more links and sources (including other stackoverflow posts) but unfortunately i only left the most relevant two because i don't have the 10 reputation required to post more.


Mixing code from this two examples i made the following unit:

http://qc.embarcadero.com/wc/qcmain.aspx?d=128968 (see comments) Calling objective C code block from delphi

unit uBackgroundiOS;

interface

uses fmx.platform.iOS,iOSapi.CocoaTypes, Macapi.ObjCRuntime, Macapi.ObjectiveC,
     iOSapi.UIKit;

const
  UIBackgroundFetchResultNewData:NSUInteger = 0;
  UIBackgroundFetchResultNoData:NSUInteger = 1;
  UIBackgroundFetchResultFailed:NSUInteger = 2;

  UIApplicationBackgroundFetchIntervalMinimum:NSTimeInterval = 0;
  UIApplicationBackgroundFetchIntervalNever:NSTimeInterval = -1;

type

  // copied from fmx.platform.iOS as it's on private declaration
  id = Pointer;
  SEL = Pointer;
  PUIApplication = Pointer;

  IMP = function( self : id; cmd : SEL; Param1 : NSUInteger ) : id; cdecl;

  function imp_implementationWithBlock( block :id ) : IMP; cdecl; external libobjc name  _PU + 'imp_implementationWithBlock';
  function imp_removeBlock( anImp : IMP ) : integer; cdecl; external libobjc name _PU + 'imp_removeBlock';

  procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application:    PUIApplication; handler : id );

  procedure initializeBackgroundFetch;

  //to test if procedure is called in background
  var fecth_string_test: string;

implementation

procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application: PUIApplication; handler : id );
var
  ahandlerimp: IMP;
begin
  //Code to perform fetch
  fecth_string_test := 'entered background code!!';

  ahandlerimp := imp_implementationWithBlock( handler ); //Create c function for block
  ahandlerimp(self,_cmd, UIBackgroundFetchResultNewData); //Call c function, _cmd is ignored
  imp_removeBlock(ahandlerimp); //Remove the c function created two lines up
end;

procedure initializeBackgroundFetch;
Var
  UIApp : UIApplication;
  Interval: Pointer;
begin
  UIApp := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);

  Interval := objc_msgSend((UIApp as ILocalObject).GetObjectId,
                            sel_getUid('setMinimumBackgroundFetchInterval:'));
//               Interval);
  class_addMethod( objc_getClass('DelphiAppDelegate') ,
                  sel_getUid('application:performFetchWithCompletionHandler:'),
                  @performFetchWithCompletionHandler,
                  'v@:@?'
  );
end;

end.

This code doesn't work. I call initializeBackgroundFetch when the application starts. I am sure I'm doing something wrong but can't figure what is it.

Also I noticed my application doesn't appear in the background allow applications in the iPhone settings, should it appear? I added the following in the info.plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
[...]
    <key>UIBackgroundModes</key>
    <array>
        <string>fetch</string>
        <string>remote-notification</string>
        <string>newsstand-content</string>
    </array>
</dict>
</plist>

Any ideas?

like image 712
Sion P.M. Avatar asked Oct 31 '22 20:10

Sion P.M.


1 Answers

iOS Working Solution

I finally got it to work under delphi 10 Seattle (you need this version, i have tried XE7 and doesn't work, I haven't tried xe8). Here are the steps:

1- Project > Options > Version Info. Check UIBackgroundModes you need (I used Fetch)

2- This is the code I used:

unit uBackgroundiOS;

interface

uses fmx.platform.iOS,iOSapi.CocoaTypes, Macapi.ObjCRuntime, Macapi.ObjectiveC,
     iOSapi.UIKit;

const
  UIBackgroundFetchResultNewData:NSUInteger = 0;
  UIBackgroundFetchResultNoData:NSUInteger = 1;
  UIBackgroundFetchResultFailed:NSUInteger = 2;

  UIApplicationBackgroundFetchIntervalMinimum:NSTimeInterval = 0;
  UIApplicationBackgroundFetchIntervalNever:NSTimeInterval = -1;

type

  // copied from fmx.platform.iOS as it's on private declaration
  id = Pointer;
  SEL = Pointer;
  PUIApplication = Pointer;

  IMP = function( self : id; cmd : SEL; Param1 : NSUInteger ) : id; cdecl;

  function imp_implementationWithBlock( block :id ) : IMP; cdecl; external libobjc name  _PU + 'imp_implementationWithBlock';
  function imp_removeBlock( anImp : IMP ) : integer; cdecl; external libobjc name _PU + 'imp_removeBlock';

  procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application:    PUIApplication; handler : id );

  procedure initializeBackgroundFetch;

  function objc_msgSend(theReceiver: Pointer; theSelector: Pointer): Pointer; cdecl; varargs;
  external libobjc name _PU + 'objc_msgSend';

  //to test if procedure is called in background
  var fecth_string_test: string;

implementation

procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application: PUIApplication; handler : id );
var
  ahandlerimp: IMP;
begin
  //Code to perform fetch HERE!!!!
  fecth_string_test := 'entered background code!!';

  ahandlerimp := imp_implementationWithBlock( handler ); //Create c function for block
  ahandlerimp(self,_cmd, UIBackgroundFetchResultNewData); //Call c function, _cmd is ignored
  imp_removeBlock(ahandlerimp); //Remove the c function created two lines up
end;

procedure initializeBackgroundFetch;
Var
  UIApp: UIApplication;
begin
  UIApp := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);

  objc_msgSend((UIApp as ILocalObject).GetObjectId,
                sel_getUid('setMinimumBackgroundFetchInterval:'),
                UIApplicationBackgroundFetchIntervalMinimum);

  class_addMethod(objc_getClass('DelphiAppDelegate') ,
                  sel_getUid('application:performFetchWithCompletionHandler:'),
                  @performFetchWithCompletionHandler,
                  'v@:@?');
end;

end.
like image 56
Sion P.M. Avatar answered Nov 13 '22 17:11

Sion P.M.