Read and writing a inifile in a Delphi Firemonkey Mobile Application? - android

If this has already been visited here on SO, please point me to it, cause I cant seem to find it. Having said that:
Using the standard delphi application events, as well as the Mobile app lifecycle events handling , i am trying to find the best spot to read and write a INI file?
as I test, i created a demo app with a button which increments a count variable and displays it in a show message
procedure TfrmMain.Button1Click(Sender: TObject);
begin
inc(Count);
ShowMessage(IntToStr(Count));
end;
In the main form's OnCreate even, I read the inifile
procedure TfrmMain.FormCreate(Sender: TObject);
var
Ini: TIniFile;
begin
Ini := TIniFile.Create( TPath.GetDocumentsPath + PathDelim + 'fortysixtozero.ini' );
try
Count := Ini.ReadInteger( 'Main', 'Count', 0 );
finally
Ini.Free;
end;
end;
Now, knowing that a mobile app can have different states, i am wondering where the best place is to write the ini file?

Best states to save the application state or store settings is "aeEnteredBackground". I used the delphi FMX event here. You should also check the "aeWillBecomeInactive" and "aeWillTerminate" events, but the first one is the most relavant. The application enters background when another application is opened or yours is closed (they are not terminated right away).
Check this article.
The code to listen for events looks like this:
function TfMain.HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
begin
case AAppEvent of
aeFinishedLaunching: ;
aeBecameActive: ;
aeWillBecomeInactive: ;
aeEnteredBackground: ;
aeWillBecomeForeground: ;
aeWillTerminate: ;
aeLowMemory: ;
aeTimeChange: ;
aeOpenURL: ;
end;
Result := True;
end;
To attach the listener you use the platform services:
if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(SvcEvents)) then
SvcEvents.SetApplicationEventHandler(HandleAppEvent);
Just add "FMX.Platform" to your uses clause.

In Delphi XE7, there is an "OnSaveState" event for forms. This is the preferred place to save application data since it will execute when an iOS app goes into the "background" state. The documentation is quite helpful... search for "save state".
Here is my code in the main form's OnCreate handler:
procedure TMainWindow.FormCreate( Sender : TObject );
var
IniFile : TIniFile;
Metric : BOOLEAN;
IniFileName : STRING;
Reader : TBinaryReader;
begin
fInitializing := True;
SaveState.StoragePath := TPath.GetLibraryPath;
if SaveState.Stream.Size > 0 then begin
Reader := TBinaryReader.Create( SaveState.Stream );
try
Metric := Reader.ReadBoolean;
vMetricUnits.IsChecked := Metric;
SetSliderLimits( Metric );
Temperature := Reader.ReadDouble;
Dewpoint := Reader.ReadDouble;
Humidity := Reader.ReadDouble;
WindSpeed := Reader.ReadDouble;
finally
Reader.Free;
end;
end
else begin
Metric := False;
vMetricUnits.IsChecked := Metric;
SetSliderLimits( Metric );
Temperature := 70;
Dewpoint := 70;
Humidity := 100;
WindSpeed := 0;
end;
SetMetricUnits( cMetricUnits );
fInitializing := False;
WriteTrackbarCaptions;
CalculateTemperatures;
end;
And here is the code in the form's OnSaveState handler:
procedure TMainWindow.FormSaveState( Sender : TObject );
var
Writer : TBinaryWriter;
begin
SaveState.Stream.Clear;
Writer := TBinaryWriter.Create( SaveState.Stream );
try
Writer.Write( cMetricUnits );
Writer.Write( Temperature );
Writer.Write( Dewpoint );
Writer.Write( Humidity );
Writer.Write( WindSpeed );
finally
Writer.Free;
end;
end;
I've tested this on both the iPad and in Windows and it works on both platforms. Doing it this way completely avoids the use of the .ini file, however it does create a somewhat oddly named .tmp file in the Windows version. I assume that an equivalent file is also created on the iPad.

Related

How to restart an app programmatically for android?

First of all, I know a little bit about restarting an app. but that's when it is for windows. In this case, I need to make this for an app that is from android. I couldn't find a solution for it that works in Delphi. Just found this from #Mihai Limbășan I quote:
Perhaps you should think outside the box. Instead of futzing with the
mutex/instance logic, you could simply create another executable that
waits for your app to close then starts it again. As an added bonus,
you can later use this mechanism to, for example, update some of your
main app's binaries. It's also much easier to run it elevated instead
of maintaining different integrity levels inside the same app, etc.
But have no idea how this works or even where to start...
Every tip, code sample, or maybe other solution to restart an app will be appreciated.
EDIT
after some questions here are some pieces of code from the procedure.
First.
after you choose for example the language 'English and push the button save this happens
Inifile := TIniFile.Create(fPath);
try
Inifile.WriteString('Instelling','ip',edit5.text);
Inifile.WriteString('Instelling','user',edit6.text);
Inifile.WriteString('Instelling','pixels',edit3.text);
Inifile.WriteInteger('Instelling','language',Combobox2.ItemIndex);
fGebruiker := Edit6.Text;
fFotoformaat := StrToInt(edit3.Text);
finally
FDConnection1.Params.Values['server']:=edit5.Text;
FDConnection1.Connected := True;
inifile.free;
End;
with this code, I fill an inifile with data as you see also the item index of the Combobox for the language.
on this point i restart the app manually so the right language is chosen by this code:
procedure TfmMain.FormShow(Sender: TObject);
VAR
param : string;
inifile : tInifile;
begin
if (System.SysUtils.fileexists(fPath)) then
Begin
begin
Inifile := TIniFile.Create(fPath);
try
if not (Inifile.ReadString('Instelling','ip','default')='default') and not (Inifile.ReadString('Instelling','Gebruiker','default')='default')then
try
edit5.text := Inifile.ReadString('Instelling','ip','default');
edit6.text := Inifile.ReadString('Instelling','user','default');
Edit3.text := Inifile.ReadString('Instelling','pixel','400');
combobox2.ItemIndex := IniFile.ReadInteger('Instelling','language',1);
fpixel:= StrToInt(edit3.Text);
fuser:=edit6.text;
FDConnection1.Params.Values['server']:=edit5.Text;
taal := 'NL';
//find language settings
if combobox2.ItemIndex=0 then
begin
language:= 'NL'
end;
if combobox2.ItemIndex=1 then
begin
language:= 'ENG';
end;
if language='ENG' then
begin
vertalerENG.vertaler('ENG');
end;
end;
end;
end;
end;
the VertalerENG is a function that is fired if the language parameter is ENG and change all of the captions to English.
the problem is that nothing is changed till i restart the app.
If you want restart app programmatically,
This code work fine for me and you can set time elapse before restart app
uses
Androidapi.Helpers,Androidapi.JNI.GraphicsContentViewText,Androidapi.JNI.App,
System.DateUtils;
...
procedure RestartApp;
{$IFDEF ANDROID}
var LPM : JPackageManager;
LIntent_Start : JIntent;
LPendingIntent : JPendingIntent;
LMS : Int64;
{$ENDIF}
begin
{$IFDEF ANDROID}
LPM := TAndroidHelper.Context.getPackageManager();
LIntent_Start := LPM.getLaunchIntentForPackage(
TAndroidHelper.Context.getPackageName()
);
LIntent_Start.addFlags( TJIntent.JavaClass.FLAG_ACTIVITY_CLEAR_TOP );
LPendingIntent := TJPendingIntent.JavaClass.getActivity(
TAndroidHelper.Context,
223344 {RequestCode},
LIntent_Start,
TJPendingIntent.JavaClass.FLAG_CANCEL_CURRENT
);
LMS := DateTimeToUnix( Now, False {InputIsUTC} ) * 1000;
TAndroidHelper.AlarmManager.&set(
TJAlarmManager.JavaClass.RTC,
LMS + 10000,
LPendingIntent
);
// TAndroidHelper.Activity.finish();
Application.Terminate;
{$ENDIF }
end;
If changing the language is your only concern then i would suggest changing the locale of the application. You only need to restart the activity if you're using all the strings correctly from strings.xml
You can see it here how to change the locale of application programatically.
Change app language programmatically in Android

MultiThreading in Delphi FMX

I'm trying to iterate though a TClientDataSet and refresh a TLabel and a TProgressBar in Android, but I get this error. How can I fix it?
This is the execute procedure. is the first time i'm working wuth multithreads in Delphi and i would like to know about that.
I hope you can help me.
procedure TThreadCatalogos.Execute;
var i : Integer;
AppPath : string;
begin
AppPath := System.IOUtils.TPath.GetPublicPath;
ProgressBar.Min := 0;
for i := round(ProgressBar.Min) to round(ProgressBar.Max) do begin
// check if Self(thread) is terminated, if so exit
if Terminated then
Exit;
Position := i;
{*******************************************}
Conexion.Open;
//CLIENTES
dsClientes.Open;
//mtClientes.EmptyDataSet;
dsClientes.First;
ProgressBar.Max := dsClientes.RecordCount;
while not dsClientes.Eof do
begin
if not mtClientes.Locate('nombre',dsClientes.FieldByName('nombre').AsString,[]) then
begin
Synchronize(procedure()
begin
mtClientes.Insert;
mtClientes.Fields[0].Value := dsClientes.FieldByName('cliente_id').Asinteger;
mtClientes.Fields[1].Value := dsClientes.FieldByName('nombre').AsString;
mtClientes.Fields[2].Value := dsClientes.FieldByName('tipo').AsString;
mtClientes.Post;
mtClientes.SaveToFile(System.IOUtils.TPath.combine(AppPath,'CLIENTES.bin'),sfBinary);
lbl.Text := 'Cliente '+floattostr(ProgressBar.Value)+' de '+floattostr(ProgressBar.Max);
ProgressBar.Value := ProgressBar.Value + 1;
end);
dsClientes.Next;
end;
//mtClientes.SaveToFile(System.IOUtils.TPath.combine(AppPath,'CLIENTES.xml'),sfXML);
mtClientes.First;
end); Exit;
end;
{************************************************}
end;
end;
To update a progress bar in a main thread from a child thread one approach is to:
Use atomically updateable global variable(s), like a 32 bit integer that you update in the child thread.
Use a TTimer event on the form that updates the progress bar based off the values in the global variable(s).
This prevents updating the progress bar too often and lets the thread do a very quick updates to progress.

How can I save the thread results in a Database

Work on a project and I want to save the images I downloadin a blob field. I use idHTTP with success but UI freezes.
I create a procedure to download images from URL using ImageLoader method (Image Loader page). Everything is OK with the images but I cannot save them in the specific field in DB.
How can I do it?
Here is my code :
procedure TForm1.LoadImages;
var
i: integer;
IMLO: TImageLoader;
IM: TImage;
Stream: TMemoryStream;
imgAddress: string;
begin
Table1.First;
for i := 0 to Table1.RecordCount-1 do
begin
imgAddress := Table1.FieldByName('URL').AsString;
Stream := TMemoryStream.Create;
IM:= TImage.Create(self);
IM.Name := 'IM'+inttostr(i);
IM.Parent := HorzScrollBox1;
IM.Position.X := i * 320;
IM.Align:= TAlignLayout.alLeft;
IM.Width := 300;
IMLO.LoadImage(IM, imgAddress);
{ Table1.Edit;
TBlobField(Table1.FieldByName('image')).LoadFromStream(Stream);
Table1.Post;
Stream.Free; }
Table1.Next;
end;
end;
The code works perfectly if I want just to show images. How can I use stream to save them?
Problem
The problem is that the TImageLoader.LoadImage loads the data from the net asynchronously.
Because of that the data is not yet available at the time when you're loading the data.
Simple solution
In order to stop TImageLoader from doing that you can add an extra method to it and call that one instead.
The other problem is that you are loading the Blob with the Stream data, but you never load the stream with anything.
Luckily you don't need the stream.
function TImageLoader.LoadImageNow(const AImage: TImage; const AImageURL: string): boolean;
const
success = true;
failure = false;
var
MS : TMemoryStream;
Image: TImage;
begin
Result:= success;
MS := TMemoryStream.Create;
Image:= TImage.Create;
try
IdHTTP1.get(AImageURL,MS);
Ms.Seek(0,soFromBeginning);
Image.LoadFromStream(MS);
AImage.Picture.Assign(Image);
except
Result:= failure;
end;
FreeAndNil(Image);
FreeAndNil(MS);
end;
Now the loader will only return when the image is loaded.
This also explains why you get an exception upon freeing IM.
You're freeing IM, but the loader is still using it in the background.
That's the problem with using multi-threaded applications, you need to keep track of what the other threads are doing.
The code for your loop will now look like this:
procedure TForm1.LoadImages;
var
i: integer;
IMLO: TImageLoader;
IM: TImage;
imgAddress: string;
FieldImage: TGraphicField;
FieldUrl: TField;
begin
FieldImage:= TGraphicField(Table1.FieldByName('image'));
FieldUrl:= Table1.FieldByName('URL');
Table1.First;
for i := 0 to Table1.RecordCount-1 do try
imgAddress := FieldUrl.AsString;
IM:= TImage.Create(self);
IM.Name := 'IM'+inttostr(i);
IM.Parent := HorzScrollBox1;
IM.Position.X := i * 320;
IM.Align:= TAlignLayout.alLeft;
IM.Width := 300;
if IMLO.LoadImageNow(IM, imgAddress) then begin
Table1.Edit;
FieldImage.Assign(IM.Picture);
Table1.Post;
end; {if}
finally
IM.Free;
Stream.Free;
Table1.Next;
end; {for .. try}
end;
Because FieldByName is slow it is best to pull it out of a loop (it probably will not matter because of the slowness of LoadImageNow).
See: http://docwiki.embarcadero.com/Libraries/XE5/en/Data.DB.TBlobField.Assign
And: http://docwiki.embarcadero.com/CodeExamples/XE3/en/HTTP_Get_%28Delphi%29
Other option
Add an event to the thread that you can hook into every time a image finishes loading.
In the event handler you can then display the image and put it into the database.
In fact the async loader may already have an option for this, but I do not know that codebase well enough to answer that question.

Delphi xe5 Android app freezes when accessing Clipboard

I am building an application for Android using Delphi XE5 that makes use of the Zxing barcode application and it uses the clipboard to retrieve the result. All of the code (Most of it anyway) is from a tutorial that I have found on the web. When I followed the tutorial, it worked to a charm but when applying the SAME code within an application that I was already working on - it did not work. Whenever accessing the clipboard ( in the 'OnTimer' event), the application always hangs and stops working. No error, nothing. App freezes and I have to close it via the phone's task manager.
The application fails right after the following line
Log.Add('AndroidClipboardScanner:1'); Log.SaveToFile(INIFileLog);
I have changed the coding around so that the app assumed the Clipboard service was available in the Ontimer event and it proceeded but it then failed after the following line:
Log.Add('AndroidClipboardScanner:4'); Log.SaveToFile(INIFileLog);
I am not sure where to begin debugging because the same code works in the other application that I created following the initial guide I found. I can also confirm that the ClipService is being assigned properly, otherwise the intent wouldn't even begin. Any help or guidance would be much appreciated ! Below is my code...
This Declared in the 'Private' variables section of the form:
ClipService: IFMXClipboardService;
This within the 'OnTimer' event for Timer1:
procedure TMain_Form.Timer1Timer(Sender: TObject);
var
barCode : String;
begin
timer1.Enabled := false;
Log.Add('AndroidClipboardScanner:0.1'); Log.SaveToFile(INIFileLog);
Try
if assigned(ClipService) then begin
Log.Add('AndroidClipboardScanner:1'); Log.SaveToFile(INIFileLog);
if (ClipService.GetClipboard.ToString <> 'nil') then
begin
Log.Add('AndroidClipboardScanner:2'); Log.SaveToFile(INIFileLog);
timer1.Enabled := false;
Log.Add('AndroidClipboardScanner:3'); Log.SaveToFile(INIFileLog);
Elapsed := 0;
Log.Add('AndroidClipboardScanner:4'); Log.SaveToFile(INIFileLog);
editHold.PasteFromClipboard;
//EditHold.Text := ClipService.GetClipboard.ToString;
Log.Add('AndroidClipboardScanner:5'); Log.SaveToFile(INIFileLog);
end else
begin
Log.Add('AndroidClipboardScanner:6'); Log.SaveToFile(INIFileLog);
Timer1.Enabled := False;
Log.Add('AndroidClipboardScanner:7'); Log.SaveToFile(INIFileLog);
end;
Log.Add('AndroidClipboardScanner:8'); Log.SaveToFile(INIFileLog);
end else begin
ShowMessage('Unexpected error has occured');
end;
Except
ShowMessage('Unexpected error has occured..');
End;
end;
Within the ONCreate procedure of the form:
if not TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService,
IInterface(ClipService)) then begin
ShowMessage('Clipboard Failed:1');
ClipService := nil;
end;
Elapsed := 0;
This is for click event for the button that begins the intent:
procedure TMain_Form.Button_ShowScannerClick(Sender: TObject);
{$IFDEF ANDROID}
var
intent: JIntent; {$ENDIF}
begin
{$IFDEF ANDROID}
//ShowMessage('Scanner:1');
if assigned(ClipService) then begin
//ShowMessage('Scanner:2');
ClipService.SetClipboard('nil');
intent := tjintent.Create;
intent.setAction(stringtojstring('com.google.zxing.client.android.SCAN'));
intent.putExtra(tjintent.JavaClass.EXTRA_INTENT,
stringtojstring('"SCAN_MODE"'));
sharedactivity.startActivityForResult(intent,0);
Elapsed := 0;
timer1.Enabled := true;
//ShowMessage('Scanner:3');
end;
{$ENDIF}
You didn't set the SCAN_MODE parameters like:
intent.putExtra(tjintent.JavaClass.EXTRA_TEXT,
stringtojstring('"SCAN_MODE","ONE_D_MODE,QR_CODE_MODE,PRODUCT_MODE,DATA_MATRIX_MODE"'));
Also you can check timer interval parameter...
I tested the solution on few devices.
With low interval value I had black screen sometimes

How can I get a phone's contact list in a Firemonkey mobile application?

How can I get a phone's contact list in a FireMonkey mobile application?
here you go .. It's not finished as it reads all numbers for one person and if there are two numbers you will have two times this person listed inside list .. but from here I think you can work and adjust it to your needs :))
function GetContact: TStringList;
var
cursorContacts, cursorContactsPhone: JCursor;
hasPhoneNumber: Integer;
id: Int64;
displayName, phoneNumber, contactID: string;
begin
Result := TStringList.Create;
cursorContacts := SharedActivity.getContentResolver.query(TJContactsContract_Contacts.JavaClass.CONTENT_URI, nil, nil, nil, nil);
if (cursorContacts.getCount > 0) then
begin
while (cursorContacts.moveToNext) do
begin
id := cursorContacts.getLong(cursorContacts.getColumnIndex(StringToJString('_ID')));
displayName := JStringToString(cursorContacts.getString(cursorContacts.getColumnIndex(StringToJString('DISPLAY_NAME'))));
hasPhoneNumber := cursorContacts.getInt(cursorContacts.getColumnIndex(StringToJString('HAS_PHONE_NUMBER')));
if (hasPhoneNumber > 0) then
begin
cursorContactsPhone := SharedActivity.getContentResolver.query(TJCommonDataKinds_Phone.JavaClass.CONTENT_URI, nil,StringToJString('CONTACT_ID = ' + IntToStr(id)),nil, nil);
while (cursorContactsPhone.moveToNext) do
begin
phoneNumber := JStringToString(cursorContactsPhone.getString(cursorContactsPhone.getColumnIndex(StringToJString('DATA1'))));
contactID := JStringToString(cursorContactsPhone.getString(cursorContactsPhone.getColumnIndex(StringToJString('CONTACT_ID'))));
Result.Add(displayName + ': ' + phoneNumber);
end;
cursorContactsPhone.close;
end;
end;
end;
cursorContacts.close;
end;
Best Regards,
Kruno
Here's my code (inspired and originally created by #mali kruno, I only changed it to my needs!) to search through all contacts based on TEdit OnChange event:
I use this function in my commonfunctions.pas unit:
function GetContact (Name: string; Number: string; const tip: integer) : TStringList;
var
cursorContactsPhone: JCursor;
Typo1, Typo2: string;
FindBy: JString;
ToFind: TJavaObjectArray<JString>;
CurRec: integer;
begin
Result:=TStringList.Create;
CurRec:=0;
ToFind:= TJavaObjectArray<JString>.Create(2);
if Name <> '' then
begin
ToFind.Items[0] := StringToJString('data1');
ToFind.Items[1] := StringToJString('display_name');
FindBy := StringToJString('display_name LIKE "%' + Name + '%"');
Typo1:='data1';
Typo2:='display_name';
end
else if Number <> '' then
begin
ToFind.Items[0] := StringToJString('display_name');
ToFind.Items[1] := StringToJString('data1');
FindBy := StringToJString('data1 LIKE "%' + Number + '%"');
Typo1:='display_name';
Typo2:='data1';
end;
cursorContactsPhone := SharedActivity.getContentResolver.query(TJCommonDataKinds_Phone.JavaClass.CONTENT_URI, ToFind, FindBy, nil, nil);
while (cursorContactsPhone.moveToNext) do
begin
Result.Add
(JStringToString(cursorContactsPhone.getString(cursorContactsPhone.getColumnIndex(StringToJString(Typo2)))) + ' - ' +
JStringToString(cursorContactsPhone.getString(cursorContactsPhone.getColumnIndex(StringToJString(Typo1)))));
CurRec:=CurRec+1;
end;
cursorContactsPhone.close;
end;
I call it from ContactSearch.Change event (it's TEdit component) like this:
procedure TMainF.ContactsSearch.Change(Sender: TObject);
var ResultNo: integer; SearchContacts: string; Results: TStringList;
begin // begin main procedure
if ContactsSearch.Text.Length > 1 then
begin //begin search and memo update
SearchContacts:=ContactsSearch.Text;
Results:=GetContact(SearchContacts, '', 0);
ResultNo:=0;
Memo1.Lines.Clear;
for ResultNo := 0 to Results.Count-1
do
begin
Memo1.Lines.Add(Results.Strings[ResultNo]);
end;
Results.Free;
end;
end;
Note, that the Result is a TStringList created in a function and freed in a procedure after Memo update.
Note also, that I only search if TEdit length is 2 or more, since otherwise entering just "a" in a tedit would show all contacts that have a letter "a" in their name, and therefore it would freeze a little every time you search, use backspace etc...
The workaround would be to load the phonebook in a TStringList on application start, and then search through the stringlist only, but that would make few other troubles:
a) phonebook update wouldn't be detected, or you'd have to implement "Update" button, which would make no sense to do the workaround at all..
b) app start would take longer
c) haven't tried that and not sure how much would it actually speed-up the search, since the Memo.Lines.Add takes more time than the query itself, so...
As for the duplicates, you can see that here are not handled, because currently I don't have a need to do so, but you can easily handle this using "sort" in a Memo, or, even better if you don't want to lose the entries that would otherwise appear as a duplicate, manage them inside a TStringList itself, so that you merge numbers in the same line, or create sub-stringlists for each name (of course, only if a name appears more than once, if you don't want to end up having twice as much stringlists as you'd actually need).
Hope this helps.
You do it in much the same way as a programmer would who uses the native programming APIs, given that Delphi does not provide a unified/wrapped solution to this problem.
You need to research how the Android SDK surfaces the contact list and how the iOS SDK surfaces its contact list, then make use of the native APIs to access it.
It will differ wildly between the 2 platforms, but it would be feaible to write some OS-independent interface to it once you've established the implementation on the 2 different OSs and seen what is on offer and what is accessible across the two implementations. This is what FMX does in other instances of similar features implemented on the two platforms.
If the required APIs haven't already been imported into Delphi's RTL, which is quite possible, then you'd also need to write the imports for those APIs you need in order to be able to call them in the first place.
Executive summary:
Roll up your sleeves
Get stuck in
Code it up yourself
Bask in the pleasure of having got some cool API stuff working

Categories

Resources