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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With