I want to list all available raw sensor data in a Memo for Android.
Following code worked over the past years, but it doesn't work with XE8. There is probably an internal compiler bug. Is there anything I can do to make it work again, or is there an alternative solution?
uses
TypInfo;
type
TOrientationSensorAccessor = class(TCustomOrientationSensor);
TLocationSensorAccessor = class(TCustomLocationSensor);
procedure TForm2.Button1Click(Sender: TObject);
var
p_location: TCustomLocationSensor.TProperty;
p_orientation: TCustomOrientationSensor.TProperty;
n, v: string;
begin
Memo1.Lines.Clear;
if Assigned(OrientationSensor1.Sensor) then
begin
if not OrientationSensor1.Sensor.Started then OrientationSensor1.Sensor.Start;
// Error (only in XE8): Incompatible types 'TCustomLocationSensor.TProperty' and 'TCustomOrientationSensor.TProperty'
// In XE7 it works.
for p_orientation in OrientationSensor1.Sensor.AvailableProperties do
begin
n := 'OrientationSensor.'+GetEnumName(TypeInfo(TCustomOrientationSensor.TProperty), integer(p_orientation)) ;
v := FloatToStr(TOrientationSensorAccessor(OrientationSensor1.Sensor).GetDoubleProperty(p_orientation));
Memo1.Lines.Values[n] := v;
end;
end;
if Assigned(LocationSensor1.Sensor) then
begin
if not LocationSensor1.Sensor.Started then LocationSensor1.Sensor.Start;
for p_location in LocationSensor1.Sensor.AvailableProperties do
begin
n := 'LocationSensor.'+GetEnumName(TypeInfo(TCustomLocationSensor.TProperty), integer(p_location)) ;
v := FloatToStr(TLocationSensorAccessor(LocationSensor1.Sensor).GetDoubleProperty(p_location));
Memo1.Lines.Values[n] := v;
end;
end;
end;
Update
Some experiments:
(1) When I comment out the first "for", it will compile:
// for p_orientation in OrientationSensor1.Sensor.AvailableProperties do
// begin
n := 'OrientationSensor.'+GetEnumName(TypeInfo(TCustomOrientationSensor.TProperty), integer(p_orientation)) ;
v := FloatToStr(TOrientationSensorAccessor(OrientationSensor1.Sensor).GetDoubleProperty(p_orientation));
Memo1.Lines.Values[n] := v;
// end;
end;
(2) When I comment out the assigning of "n" and "v", it will compile too:
for p_orientation in OrientationSensor1.Sensor.AvailableProperties do
begin
// n := 'OrientationSensor.'+GetEnumName(TypeInfo(TCustomOrientationSensor.TProperty), integer(p_orientation)) ;
// v := FloatToStr(TOrientationSensorAccessor(OrientationSensor1.Sensor).GetDoubleProperty(p_orientation));
// Memo1.Lines.Values[n] := v;
end;
end;
Since neither "for", nor "n" and "v" is the bad region, where is the error then?
(3) When I comment out the second for-loop, it will compile again. If I comment out the first for-loop, it will compile too. Each for-loop works, but in combination they will not work.
It looks like the error is only happening if 5 factors are combined:
TypInfo
Accessors
for loop
Usage of TypInfo (GetEnumName)
Both for-loops are used.
Update 2
Here is the smallest reproducible code I could find. If any line is commented out, it compiles:
program ProjectCompilerBug;
{$APPTYPE CONSOLE}
uses
System.Sensors, System.Sensors.Components;
var
p_location: TCustomLocationSensor.TProperty;
p_orientation: TCustomOrientationSensor.TProperty;
begin
// Compilation Error (only in XE8):
// "Incompatible types 'TCustomLocationSensor.TProperty' and 'TCustomOrientationSensor.TProperty'"
// In XE7 it compiles
for p_orientation in TOrientationSensor.Create(nil).Sensor.AvailableProperties do
begin
FloatToStr(1.23);
end;
for p_location in TLocationSensor.Create(nil).Sensor.AvailableProperties do
begin
end;
end.
Yes, this looks like an XE8 compiler bug. I think you've done a fine job isolating it, for which I commend you. You'll need to submit bug report to Quality Portal.
To workaround the fault I think you will be able to put the loops in separate functions. My hypothesis is that the key is the presence of two for in loops with differently typed loop variables that is the key. Avoid that and you should be able to avoid the problem.
Related
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
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.
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.
Problem
I have 2 different json arrays that looks like this:
1 - Events List
{"0000:First Event Name","0001:Second Event Name","0002:Third Event Name"}
2 - Available Events List
{"0001","0002"}
and then I need to generate a ListBox with the available events using checkboxes:
[ ] First Event Name
[x] Second Event Name
[x] Third Event Name
Given solution
procedure TFormHome.GetEvents(Sender: TObject);
var
K: Integer;
Z: Integer;
ListCount_Events : Integer;
AvailableList_Count : Integer;
lb_item: TListBoxItem;
event_code : string;
event_code_1: string;
event_name : string;
begin
// Check if the JSON responses are not nil
if ((json_response_events <> nil) and (json_response_available_events <> nil)) then
begin
ListCount_Events := json_response_events.Count;
// Get Available List Count
AvailableList_Count := json_response_available_events.Count;
try
// Run a for loop to create the events based on ListCount_Events
for K := 0 to (ListCount_Events - 1) do
begin
// Get complete Event Code
event_code_1 := StringReplace(json_response_events.Items[K].ToString.Split([':'])[0], '"', '', [rfReplaceAll]);
// Get complete Event Name
event_name := StringReplace(json_response_events.Items[K].ToString.Split([':'])[1], '"', '', [rfReplaceAll]);
// Create the ListBoxItem
lb_item := TListBoxItem.Create(self);
// Assign it to the the ListBox component
lb_item.Parent := lb_notifications;
// ListBoxItem get the event name
lb_item.Text := event_name;
// Remove StyledSettings (Other)
lb_item.StyledSettings := lb_item.StyledSettings - [TStyledSetting.Other];
// Remove StyledSettings (FontColor)
lb_item.StyledSettings := lb_item.StyledSettings - [TStyledSetting.FontColor];
// Change TextSettings FontColor to default
lb_item.TextSettings.FontColor := $FF626262;
// Set selectable to false in order to not permit the user
// to select multiple items on the List
lb_item.Selectable := false;
// Set the appropriated style
lb_item.StyleLookup := 'listboxitemleftdetail';
// Run a for loop to check the available events
for Z := 0 to (AvailableList_Count) do
begin
event_code := StringReplace(json_response_available_events.Items[Z].ToString, '"', '', [rfReplaceAll]);
if event_code_1.Contains(event_code) then
begin
if K < ListCount_Events then
begin
// Remove StyledSettings (FontColor)
lb_item.StyledSettings := lb_item.StyledSettings - [TStyledSetting.FontColor];
// Change TextSettings FontColor to available
lb_item.TextSettings.FontColor := $FF179ADF;
// Set the List CheckBox to checked
lb_item.IsChecked := true;
end;
end;
end;
end;
finally
begin
// Call to List start at position 0
lb_notifications.ItemIndex := 0;
end;
end;
end;
end;
I think my code smells and I want to know why and learn to do better.
Reasonable suggestions are welcome!
It takes too long to load the list on both Android and iOS.
When scrollable, it is incredibly slow on Android (but not on iOS).
How can I accelerate filling up this list?
Following the suggestion by #birger, it was solved with 2 simple lines of code:
BeginUpdate;
//
GetEvents;
//
EndUpdate;
If you use a Delphi component like a ListBox, Memo, ListView,... and you add or modify a lot of items (lines, nodes,...), the component's performance becomes very slow. This is due to the fact that after each change, it is redrawn on the screen.
BeginUpdate and EndUpdate prevent excessive redraws and speed up processing time when items are added, deleted, or inserted.
Reference: http://www.festra.com/eng/tip-beginupdate.htm
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