Jump to content

KMarb

Members
  • Content Count

    77
  • Joined

  • Last visited

  • Days Won

    1

Posts posted by KMarb


  1. awesome! Thank you. Follow up question:

     

    I'm testing on an android 11 device, a uleFone Armor X9. The permission handler shows that both read and write external storage have been granted.

     

    I'm getting a permission error when I try to download a file to the shared downloads folder:

     

      if (Length(AGrantResults) = 2) and (AGrantResults[0] = TPermissionStatus.Granted) and (AGrantResults[1] = TPermissionStatus.Granted) then
        begin

    // both are granted...

      UpdateFileName := extractFNFromURL (UpdateURL);
      UpdateFullFilename := TPath.Combine(TPath.GetSharedDownloadsPath, UpdateFileName);

     

      // I get a permission denied on the following line, not sure if the error is triggered by the fileExists function or request to DeleteFile.

      if fileExists (UpdateFullFilename) then TFile.Deletefile (UpdateFullFilename);

     

      Is this possibly related to android 11 changes with shared storage?


  2. My android app (Delphi 11) wants to download a file to the external storage download folder. I call the line below:

     

      PermissionsService.RequestPermissions([FPermissionReadExternalStorage, FPermissionWriteExternalStorage], ExternalStoragePermissionRequestResult, externalStorageRationale);

     

    I understand the call to requestPermissions happens asynchronously. When user accepts or rejects the request, the following handler runs:

     

    procedure TDMCommon.ExternalStoragePermissionRequestResult(Sender: TObject; const APermissions: TClassicStringDynArray; const AGrantResults: TClassicPermissionStatusDynArray);
    begin
      if (Length(AGrantResults) = 2) and (AGrantResults[0] = TPermissionStatus.Granted) and (AGrantResults[1] = TPermissionStatus.Granted) then
        begin
          toast ('Downloading file...', shortToast);

          AsyncDownload.Download(FileURL, LocalFilename, asyncDownloadFinished) then
    ...

     

    I've chopped out code I feel is not relevant.

     

    Here are my questions:

      1 - The handler is not running in the main thread, correct?

      2 - If 1 is correct, then I should not call "toast" directly like it's shown, correct? I should call TThread.Queue(nil, procedure begin toast... end);?

      3 - I'm downloading a file in the background using the AsyncDownload code (taken from one of the Delphi samples). It works great when run from main thread, but, if the handler is running code in a secondary thread (not the main thread), is it a bad idea to kickoff another thread to download a file? Could I just initiate a synchronous download here?

     

    I'm having some strange behavior and trying to get things right.

     

    The strangeness:

      - running in debug mode, sometimes I end up with a "CPU" window where I'm stepping through assembly code... I'm not sure what can cause that.

      - I set breakpoints in the finally section of "Try" blocks, but the breakpoints never fire, as if the "finally" blocks are not actually executing. I have calls to log.d in the finally and they do execute, so I think things are fine, but execution does not pause at my breakpoint.

     

    Any light on the subject is much appreciated.

     

    Keith


  3. I've solved my problem by adding a timer that I call in the finally block that does the things listed in the finally. It runs with a 50ms so I don't notice any delays with the screen. This required me to save the current editor and caret position in form variables, and the code is much uglier than I'd like, but I will take working over pretty any time.


    first two lines of ChangeTracking event:

      HackEdit := TEdit (sender);  // HackEdit is a form var of TEdit

      HackEditSaveCaretPos := HackEdit.CaretPosition;
     

    In the finally I have this now:

        TEdit (sender).OnChangeTracking := saveEvent;
        if iTotal > 0 then
          tmrHackEdit.Enabled := true;

     

    and the timer does this:

      tmrHackEdit.Enabled := false;

      if assigned (HackEdit) then
        begin
          if (HackEditSaveCaretPos >= 0) then
            HackEdit.CaretPosition := HackEditSaveCaretPos
          else
            HackEdit.GoToTextEnd;

          HackEdit.SelStart := HackEdit.CaretPosition;
          HackEdit.SelLength := 0;
        end;
     

    All works as it should now.


  4. I have a TEdit for user to enter a job number. Job numbers vary in format, but one format is yy-xxxx, where yy is the year, so 22-1437 is a valid job number.

     

    The popup keyboard on android sometimes does not list a minus sign (dash) when entering numbers. In the past I programmed the edit control to take any of these characters (comma, *, #, space) and to convert them to a dash on the fly.

     

    Ideally I could do this in KeyUp or KeyDown or KeyPress (if it existed in FMX but it does not), and just swap characters. I haven't been able to make that work for some reason, so I'm resorting to the OnChangeTracking event.

     

    My code is below. IT is way longer than I would need if I could intercept and change character in KeyUp or KeyDown.

     

    The problem I have is, say user types 22#. # is replaced with -, so TEdit now holds 22- and the caret is flashing after the -. When user types the next number, it appears at the START of the edit field, not after the - as expected. On screen the caret is where it should be right up until I type the number after the -.

     

    This is one of those that I've spent a couple hours on and it should be trivial. Maybe it is. Please advise.

     

    procedure TfrmMain.edtJobChangeTracking(Sender: TObject);
    var
      s : string;
      i1, i2, i3, i4, imax : integer;
      iTotal : integer;
      savePos : integer;
      saveEvent : TNotifyEvent;
    begin
      saveEvent := TEdit (sender).OnChangeTracking;
      TEdit (sender).OnChangeTracking := nil;

      try
        savePos := TEdit (sender).CaretPosition;

     

        s := TEdit (sender).Text;

        i1 := s.IndexOf (' ');
        i2 := s.IndexOf ('#');
        i3 := s.IndexOf ('*');
        i4 := s.IndexOf (',');

     

        iTotal := i1 + i2 + i3 + i4 + 4;  // -1 from indexOf means not found

     

        if i1 >= 0 then
          s := s.Replace (' ', '-', [rfReplaceAll]);

        if i2 >= 0 then
          s := s.Replace ('#', '-', [rfReplaceAll]);

        if i3 >= 0 then
          s := s.Replace ('*', '-', [rfReplaceAll]);

        if i4 >= 0 then
          s := s.Replace (',', '-', [rfReplaceAll]);

     

     

        if iTotal > 0 then
          TEdit (sender).Text := s;

     

        RunRefSearch;  // this is the query in database to see if we found a match
      finally
        if iTotal > 0 then
          begin
            // 9/17/22 - cannot get this to work right...
            // user types 22#... display shows 22- which is what I want
            // but next character appears at START of edit field, not after the -
            if (savePos < 0) or (savePos = s.Length) then
              TEdit (sender).GoToTextEnd
            else
              TEdit (sender).CaretPosition := savePos;

     

            TEdit (sender).SelStart := TEdit (sender).CaretPosition;
            TEdit (sender).SelLength := 0;
          end;

     

        TEdit (sender).OnChangeTracking := saveEvent;
      end;
    end;
     


  5. form1.Invalidate does not update the label while the loop is active. On android, even application.processmessages does not update the UI. Conclusion: If you need to do a second or three of processing on android, and you want to do this in the main form, you cannot update the screen in any way while you do that short processing. Please correct me if that is wrong. I don't need someone to explain that I should do the 1-3 seconds of processing in a thread, I understand that, but sometimes the nature of the processing might make threading quite a bit more work than what would seem necessary.


  6. Delphi 11.1, creating android apps

     

    In researching other topics I found some discussion that android is a different beast in terms of memory management, and maybe we should no longer call free or freeAndNil?

     

    I haven't had an issue until now... The following code blows up:

      freeAndNil (SyncCDS);
      freeAndNil (SyncQuery);
     

    When earlier in the execution, I called this code:

     

      SyncQuery := TFDQuery.Create (nil);
      SyncQuery.Connection := DMConn.dbcMain;

      SyncCDS := TClientDataSet.Create (nil);

     

    Not sure it matters but this code is running in a TTask.Run procedure.

     

    Can someone please explain the (new) mindset around allocating and freeing objects in android please.

     


  7. Tried label1.ApplyStyleLookup in the loop, but it does not update the label while loop is running, only when done it changes to "Step 10". This is somewhat academic at this point, for me, because I'm moving my long running code to threads, but so far there doesn't appear to be away to force a label to update within the context of constant main thread processing.

     

     for i  := 1 to 10 do
        begin
          label1.Text := 'Step ' + intToStr (i);

          label1.ApplyStyleLookup;

          sleep (200);
        end;


  8. Your answer I think applies to a multi-threaded app, but not one where there is only one main thread. My need is to update the display in the context of a long-running process in the main thread, say 2-3 seconds of processing that must happen in the main thread. I know that is not proper design, and that anything that makes the UI unresponsive should be run outside the main thread, but my question is not about proper design, it is about updating the screen while the main thread is doing some processing. Thanks for your reply, though, always good to see how people solve problems.


  9. Not really sure how a timer helps. When the timer fires I would still need a way to say:

    label1.RepaintYourselfNowAlready;

     

    Can you elaborate and explain how a timer fits in with this loop, to make the label update every .2 seconds?

     

     for i  := 1 to 10 do
        begin
          label1.Text := 'Step ' + intToStr (i);

          // what to do here to make label1 show new value on the screen?
          sleep (200);
        end;


  10. Repaint does not work - I should have said I tested it, sorry. So far, I cannot find a way to update the label in this simple loop. Any other ideas?

     

    I am learning TTask.Run and making progress, and it does seem like TThread.Synchronize updates the UI well enough, but I have left an option in the app to not use TTask.Run (especially when I first have users run the new version), so it can run in the same mode (non-threaded) as I originally wrote it 7 years ago. Hence the need to do force a control to update/repaint during the course of other processing.

     

    Does anyone know how to force label1 to update in the loop below?

     

     for i  := 1 to 10 do
        begin
          label1.Text := 'Step ' + intToStr (i);

          // what to do here to make label1 show new value on the screen?
          sleep (200);
        end;


  11. Delphi 11.1 create android app...

     

    Say I have a label on my form, label1

     

    and I have a loop like this:

     

      for i  := 1 to 10 do
        begin
          label1.Text := 'Step ' + intToStr (i);
          application.ProcessMessages;
          sleep (200);
        end;
    end;

     

    Running this on Windows, the label updates while the loop is running. Without application.processmessages, the label does not change until the loop ends. I'm just trying to give some feedback to user while things are happening.

     

    What can I use in place of application.processMessages so that the label is updated on Windows and android? I've searched and haven't found a good answer.


  12. Related question: TTask.Run seems like a very simple thing to implement, simpler than local service. And it looks like tasks created with TTask.Run run in a separate thread, so the UI does not get blocked.

     

    In my case, where I have some time-consuming things that need to get done (sending and receiving data from back end database), does TTask.Run make more sense than a local service? The big thing I'm unsure of: if my app moves to background, will the task continue to run and complete a 10-15 second transfer?


  13. Thank you. Is your answer that it is impossible (or very difficult) to create a stable android app using Delphi? I have three Berlin-era apps that work well and have for about 7 years (only used within my company), but it is harder and harder to find devices running versions of android that allow the apps to run. So it is time for something different, and the most obvious path forward is to rebuild it in Alexandria. But another option is to start over completely with a different environment (Android Studio maybe) and build from scratch. The second option would be too much for me to do, so I would need to hire someone to do that, and then I would not have the same control over features and updates that I have now.

     

    Do you have a recommendation for me for how to move forward? Maybe an android developer who can build fairly simple apps for reasonable cost. Apps have local database with reference tables (lists of vendors, inventory, jobs, etc) that are updated from  tables that live on an SQL server, retrieved using a datasnap server, and transaction are submitted back to the datasnap server (work activity, photos for various steps of projects, material pulls from inventory).

     

    Developing android apps in Delphi is horrible compared to VCL, but is it doable?


  14. I have some database operations that take 10-15 seconds sometimes, and so I'm researching how to create a local service to run those ops in a separate thread. The download service demo seems like a good place to learn, but it will not run on any of my devices. I will keep researching this and add notes here if I find the problem, but can someone enlighten me as to why this demo does not work?


  15. In researching how to get my android app updated from Berlin to Alexandria, I have come across some posts of people saying that using FMX.xxx units is a problem, android apps will not work well or will crash.

     

    see this post for example: log.d not working in Android services (Delphi Berlin) - Stack Overflow

     

    Can someone comment on that? Was that a problem at one time? Or is it a problem still? If I don't use FMX.xxx units, what should I use instead?


  16. In prior apps built with Berlin I have gone through some efforts to make sure edit controls were visible to user when the on-screen keyboard pops up. If I recall correctly, I use a scrollbox, sized as if it was align to alClient, but align set to alNone. That allows me to slide the entire scrollbox up if needed when the keyboard appears.

     

    I'm just wondering what the current approach is for managing edit fields when they are low enough on form to be covered by the keyboard. Are there any articles I can read for best practices with keyboard but also for delphi android form design in general? Thanks for any pointers you have.


  17.  "Request install packages" was the final piece! saving the apk to app-specific folder works but so does downloading to shared documents folder. I prefer the shared doc folder because user can find those downloads if something goes wrong.

     

    With that setting, when the app calls the intent to install update, I get a window that says the app does not have that permission, but a click takes me to the settings where I can turn on the permission, then go back to app and click install.

     

    This is a major win for my app! Thank you so much.

     

    I will definitely check out Device Lens. Thanks again, happy weekend!

    • Thanks 1

  18. Question: I know in recent versions of android, to install an app manually that does not come from play store, you have to enable permissions on the device for the app in question (like Files app, or Opera or whatever) so that you can even install the app.

     

    How do I know if my app has permission to install an apk?

     

    Does it matter if the apk is in shared downloads or if it is in my sandboxed documents?

     

    Also, can my app install any apk, or can I only initiate the install for apk for same app as what I'm running?

     

    If this is all explained in android docs please let me know a good starting point. Thank you again.


  19. Thank you very much. Progress, but still problems.

     

    On my Samsung Galaxy S6 running 64-bit Android 7 - IT WORKS! The "Do you want to install an update..." window pops up like it should. Oddly it is full screen, not just a portion of window like when run manually.

     

    On two other 64-bit android 11 devices - same as before. the update prompt window never shows. If I go to shared downloads folder and run the just-downloaded apk manually, it works as expected, so the apk file is downloading correctly.

     

    Lastly, on a 32-bit android 9 phone - same as the android 11's - no window pops up.

     

    I had only been testing on android 11, but I wanted to test on more devices before reporting back, so it might have worked on the S6 (android 7) with prior versions of code.

     

    I'm wondering if since my original work on this was in Berlin if there are app or main form settings that are preventing the intent from running properly on newer versions of android. Can you think of any other things to look at?

     

    And a broader question, I see people who write log messages somehow/somewhere which I would think would be a great help sometimes to figure what's wrong. Is there an article that talks about setting that up?


  20. Ok. I did that and saw how the new provider_paths.xml was placed in the Android64\Debug folder, and also added to deployment page.

     

    Now when I run the app, the call to TAndroidHelper.Context.startActivity(Intent); seems to go to la-la land... No window pops up asking if I want to update, like it would if I ran the apk from the downloads folder manually.

     

    I do see my app state go through WillBecomeInactive, then BecameActive, but no prompt to install/update and no error message.

     

    Here is an important detail maybe... the apk I'm launching is the same version I'm running... it is not actually an update. Could that be why it doesn't show the install window? Or could it be a permission problem still with launching an apk from my app?

     

    Your help is really appreciated.


  21. Thanks! I have removed the provider section from manifest. I do have secure file sharing checked in entitlements list.

     

    With the change you suggested my error has changed:

     

    Project JCMTime.apk raised exception class EJNIException with message 'java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority com.zunna.JCMTime.fileprovider'.

     

    Unless you have a quicker solution I will try the steps laid out here: android - Couldn't find meta-data for provider with authority - Stack Overflow

    Although... that seems almost like the same xml I had before?


  22. In 2015 or so I used Berlin to create some android apps. I'm primarily a Windows developer, so that was a painful process. I need to update those apps so they run on new android versions, but I'm running into several issues. I am happy I found this group, I have found a lot of good advice, but I have a feeling like answers change every couple of years, and so far I have not figured out a few things.

     

    I know very little about android development, which is why I struggle, so any direction where I can learn more is much appreciated.

     

    First thing is, I want my app to self-update by downloading an apk and running so user can install if wanted.

     

    I have async download working, so that is good (threads are also new to me). My code that runs the update is below. I get the following error:

     

    Project JCMTime.apk raised exception class EJNIException with message 'android.os.FileUriExposedException: file:///data/user/0/com.hello.world/files/BigTime64-4.1.apk exposed beyond app through Intent.getData()'.

     

    Here is one version of the code I've tried:

    procedure TDMCommon.initiateUpdate;

    var
      F: Jfile;
      Intent: JIntent;
    begin
      F := TJfile.JavaClass.init(stringtojstring(UpdateFolder),stringtojstring(UpdateFN));

      Intent := TJIntent.Create();
      Intent.setAction(TJIntent.JavaClass.ACTION_VIEW);
      Intent.addFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK);
      Intent.setDataAndType(TJnet_Uri.JavaClass.fromFile(F), StringToJString('application/vnd.android.package-archive'));

      TAndroidHelper.Context.startActivity(Intent);
    end;
     

    I've tried adding the below to manifest file:

            <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.hello.world.fileprovider"
            android:exported="false"
                android:grantUriPermissions="true"
                android:exported="false">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/filepaths" />
            </provider>
     

    with following filepaths.xml file deploying to res\xml\:

     

    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <root-path name="root" path="." />
    </paths>

     

    but I'm not even sure I need those things.

     

    Any feedback is appreciated... I think the biggest problems I have are 1) lack of active delphi discussion groups, 2) programming that worked 2-3 years ago no longer works, so even when I find a relevant article/post it doesn't mean hooray, and 3) I am really uneducated about android development. Thanks for any feedback.

×