Jump to content
hackbrew

App persistence and restoring state

Recommended Posts

Currently, I'm working on an Android 13 FireMonkey mobile app (Delphi 11.3) with a TabControl with about four tabs. The app is grabbing JSON data from a URL and manually parses and loads the data into a FDMemTable. The app has very small data requirements (ie., single user, single table, and the JSON file would exchange only about 25 records per day containing about a dozen or so key/value pairs).
Besides the memory table, I'm using an embedded database (SQLite) to store the data on device using LocalSQL features to implement database CRUD operations.


My concern is persistence and restoring state if the app were to say crash/lose focus/device reset. Right now each time the app gains focus it starts from the beginning. For example, when running the app and then a call comes in on the device, my app loses its focus. When you return back to the app it starts as in the first time. I would like it if when the app restarts to remain in the same state it was before going to the background.


I played around with the OnSaveState event on my main form, but it didn't behave the way I expected. For example, I send a mapping intent from my app to the default mapping app on the device for navigation. In this instance, the user temporarily leaves my app when the mapping app opens, and when closed, the user should return to my app. When I implement the OnSaveState event into my app (to read/write data to a stream) the user never returns to my app after closing the mapping app. But, if I remove the OnSaveState event, it does return back to my app, which is the desired behavior. 

 

How should persistence and restoring state be handled in an Android app?

Share this post


Link to post

@Der schöne Günther Well, I have minimal code in the OnSaveState event.

procedure TMainForm.FormSaveState(Sender: TObject);
var
  W: TBinaryWriter;
begin
  SaveState.Stream.Clear;
  // Save app data to restore state later.
  W := TBinaryWriter.Create(SaveState.Stream);
  try
    ShowMessage('FormSaveState fired');
  finally
    W.Free;
  end;
end;

In my FormCreate event, I have:

SaveState.StoragePath := TPath.GetHomePath; // Required to make this save data persistent

if SaveState.Stream.Size > 0 then
  begin
    R := TBinaryReader.Create(SaveState.Stream);
    try
      ShowMessage('R: ' + R.ReadString);
    finally
      R.Free;
    end;
  end;

Is there something I'm missing because I'm never seeing the "FormSaveState fired" message displayed? 

Edited by hackbrew
Additional info

Share this post


Link to post

Well, have you tried just having an empty implementation of TMainForm.FormSaveState(..)?

 

I have not tried, but I would assume that when OnSaveSave gets called, you app has already been sent to the background and is about to be suspended within the next few milliseconds. Instead of actually saving something, your code example is now stuck on waiting for a confirmation for a non-visible dialogue. I would assume that the operating system either discards your handler or maybe entirely termiates your app because it appears stuck.

Edited by Der schöne Günther

Share this post


Link to post

@Der schöne Günther After terminating the mapping app (Google or Waze), the focus will return to my app right where I left off, which is the desired outcome. Conversely, if the OnSaveState event is added to the MainForm and I exit the mapping app, I'm returned to the Home screen.

Share this post


Link to post
1 minute ago, hackbrew said:

Conversely, if the OnSaveState event is added to the MainForm and I exit the mapping app, I'm returned to the Home screen.

Thank you, that clarifies it. I don't think this is intended behaviour and probably a bug.

Share this post


Link to post
28 minutes ago, hackbrew said:

 Is the data wiped from the memory table and/or embedded database (SQLite) if the app were to say crash/lose focus/device reset?

by default, the all "SaveState" is transitory (tmp), then, you can try save it permanently on disk using:  xxSaveState;Name := 'xxxxx';   xxSaveState.StoragePath := 'xxxx';

like this:

  • create your FOLDER before save it!!!!
  • ALL components should exists on form as exists on SaveState file!!!
// here Im not take care about "AV" ok?  do it yourself...

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
begin
  // you can use this on "OnCreate" from "form1"... or any place that you desire!
  //
  SaveState.Name        := 'mySaves.TMP';
  SaveState.StoragePath := 'd:\mytmpSaveStateDir';
  //
  if SaveState.Stream.Size > 0 then
    begin
      for var i: integer := 0 to (ComponentCount - 1) do
        SaveState.Stream.ReadComponent(Components[i]); // reading...
      //
      ShowMessage('SaveState size on disk: ' + SaveState.Stream.Size.ToString);
    end
  else;
end;

procedure TForm1.FormSaveState(Sender: TObject);
begin
  // 1º FormCloseQuery
  // 2º FormClose
  // 3º FormSaveState <----
  // 4º FormDestroy
  // ...
  SaveState.Stream.Clear;
  SaveState.Name        := 'mySaves.TMP'; // in disk, it will be  [ ~project1_ + "your name to SaveState" ]
  SaveState.StoragePath := 'd:\mytmpSaveStateDir';
  //
  for var i: integer := 0 to (ComponentCount - 1) do
    SaveState.Stream.WriteComponent(Components[i]); // saving all components on "Form1"
  //
  ShowMessage('FormSaveState fired');
end;

end.

 

bds_3EHxt4AeAL.gif   image.png.ae264fe78968e6367660d470a68ffc0d.png

Edited by programmerdelphi2k

Share this post


Link to post

Another thing:

  • as you use FDMemTable (I think), then, you can save your data on JSON/XML file using the properties for that:
    • ResourceOptions ->PersistentFileName + Persistent=true
    • then, when you re-open your table, the data should be there!

Share this post


Link to post

@programmerdelphi2k Based on what you've shown here, it looks like the OnSaveState event appears to work for a VCL application, but from what I'm seeing there are conflicts with apps running on Android OS. I can successfully launch and send data to the mobile devices mapping app using an intent from my app, but it will not return back to my app after the termination of the mapping app when I use the OnSaveState event for some reason.

 

When I click on a navigate button from my app, I leave my app and my default mapping app opens. Upon closing/terminating the mapping app, the focus will return to my app right where I left off, which is the desired outcome. If the OnSaveState event is added to the MainForm, when I exit the mapping app, I'm returned to the Home screen, not to my app which is a problem.

 

 

Share this post


Link to post
10 minutes ago, hackbrew said:

If the OnSaveState event is added to the MainForm, when I exit the mapping app, I'm returned to the Home screen, not to my app which is a problem.

and if you "assign" to current FORM active? unassign for any other... or be, only when you go to MAPs, you assign it, later unassign it!

  • the app above is a FMX in MSWindows, not VCL!!!! then when app end, the SaveState is called as expected
Edited by programmerdelphi2k

Share this post


Link to post

@programmerdelphi2k Yeah sorry, meant MSWindows. Do you know if this is a known bug in Delphi 11.3 for Android apps when shelling out to another app via intent and expecting a return to the main app upon closing?

Share this post


Link to post
58 minutes ago, programmerdelphi2k said:

Another thing:

  • as you use FDMemTable (I think), then, you can save your data on JSON/XML file using the properties for that:
    • ResourceOptions ->PersistentFileName + Persistent=true
    • then, when you re-open your table, the data should be there!

@programmerdelphi2k Thanks, I didn't know those settings were there.

 

On an app close or device power off/on, what happens to data in an on-device local SQLite table?

Share this post


Link to post
13 minutes ago, hackbrew said:

On an app close or device power off/on, what happens to data in an on-device local SQLite table?

let's imagine a "regular case" = no critical-crash on the system!

  • the app, would try receive the "message": hey app goes down! then, the form/app/OnClose...Destroy will be trigged
    • this can be seen on MSWindows because it's more easy, on Android it's necessary more knowledge about how happens... I dont know! but you can do the tests...
  • then, if you have the events "OnClose" (for example), you can to do some "quick"-task ... not save 1BILLION records of course!
  • SQLite it's a DB one-file with many internal resource, then, you need read the documentation about your doubts.
20 minutes ago, hackbrew said:

Do you know if this is a known bug in Delphi 11.3 for Android apps

maybe not, maybe yes... maybe your code be the "bug"... needs more details/code and somebody that know how Android works behind the scenes...

 

your code, how do you call the another app into your app? it's possible show the code?

Share this post


Link to post
8 hours ago, hackbrew said:

When I implement the OnSaveState event into my app (to read/write data to a stream) the user never returns to my app after closing the mapping app

It's possible it's because your app crashed. When switching back to it, does it appear as if the app is restarting?

I seem to recall OnSaveState being unreliable anyway due to it not being called at all if the app is "swipe closed" (I could be wrong). It might be more reliable to persist anything you need restored as soon as a change has occurred.

 

2 hours ago, hackbrew said:

On an app close or device power off/on, what happens to data in an on-device local SQLite table?

Using FireDAC, SQLite databases are by default in auto-commit mode, i.e. any changes should be saved immediately. 

Share this post


Link to post
3 minutes ago, Dave Nottage said:

It's possible it's because your app crashed. When switching back to it, does it appear as if the app is restarting?

I seem to recall OnSaveState being unreliable anyway due to it not being called at all if the app is "swipe closed" (I could be wrong). It might be more reliable to persist anything you need restored as soon as a change has occurred.

 

Using FireDAC, SQLite databases are by default in auto-commit mode, i.e. any changes should be saved immediately. 

@Dave Nottage Yes, it appears, as best I can tell that the app is restarting. There's not too much data input on each tab or objects to deal with. So after not getting the OnSaveState to behave consitently, I've resorted to writing to a file to handle state management. Thanks!

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×