Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application.Quit() closes application before SharedPreferences in-memory changes are committed to disk. #27585

Open
NathanielJS1541 opened this issue Feb 5, 2025 · 3 comments · May be fixed by #27635
Labels
area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info platform/android 🤖 s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Milestone

Comments

@NathanielJS1541
Copy link

NathanielJS1541 commented Feb 5, 2025

Description

Calling Application.Quit() immediately after using the Preferences API to Set() changes will cause the changes to be lost. This is because the Preferences API on Android uses the SharedPreferences.Editor.apply() method, which writes changes to memory and begins asynchronously writing the changes to disk.

According to the Android documentation on the SharedPreferences.Editor.apply() method:

You don't need to worry about Android component lifecycles and their interaction with apply() writing to disk. The framework makes sure in-flight disk writes from apply() complete before switching states.

This seems to suggests that even if the app is closed, any changes applied using an ISharedPreferencesEditor should be allowed to finish writing to disk before the app is closed. This seems to work with the standard app lifecycle (i.e. being dismissed or closed by the user), but not after calling Application.Quit().

It's also worth noting that calling Application.Quit() doesn't cause the OnDestroy() delegate to be invoked either. Using something like Platform.CurrentActivity.FinishAffinity() does cause the OnDestroy() delegate to be invoked, but it still won't ensure that SharedPreferences edits are committed to disk before the app closes.

Steps to Reproduce

  1. Clone my repro project.
  2. Build and deploy the app to an Android device. Note that although this worked in Debug for me, since it is time-sensitive you may need to build using the Release configuration depending on your hardware.
  3. Once the app launches, click the button that just says "Click to close the app". This button will write the current time to the Preferences API and the close the app using Application.Quit().
  4. Launch the app again. You should see that the "Last Quit Time" has not been updated. This indicates that the Preferences API applied the changes in memory, but they were never committed to disk.
  5. Now click the button that says "Click to close the app with workaround". This button will again write the current time to the Preferences API, and then block using ISharedPreferencesEditor.Commit() until the changes are written to disk before calling Application.Quit().
  6. Launch the app again. You should now see that the "Last Quit Time" has been updated, indicating that the Preferences API applied the changes in memory, and then was able to finish committing the changes to disk.

Link to public reproduction project repository

https://github.com/NathanielJS1541/MauiApplicationQuitBug

Version with bug

8.0.100 SR10

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Presumably all Android versions. Tested on Android 8.1, Android 12, and Android 14.

Did you find any workaround?

Before calling Application.Quit(), you can use the following code to block until the asynchronous disk writes are finished:

// HACK: Block until all SharedPreferences changes are written to disk.
using var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Android.App.Application.Context);
using var editor = sharedPreferences?.Edit();
editor?.Commit();

// Close the app.
Application.Quit()

Note

Although PreferenceManager.GetDefaultSharedPreferences() is marked as deprecated, it is still in use in Precerences.Android.cs in the MAUI source.

This workaround assumes you are using the default SharedPreferences file. If not, use the following:

// HACK: Block until all SharedPreferences changes are written to disk.
// TODO: Add preferences file name here...
using var sharedPreferences = Android.App.Application.Context.GetSharedPreferences("YourFileNameHere", FileCreationMode.Private);
using var editor = sharedPreferences?.Edit();
editor?.Commit();

// Close the app.
Application.Quit()

This works because, according to the Android documentation on ISharedPreferencesEditor.Apply():

If another editor on this SharedPreferences does a regular commit() while a apply() is still outstanding, the commit() will block until all async commits are completed as well as the commit itself.

Therefore, as long as you call Commit() on a SharedPreferences editor for the same file you applied changes to, it will block until all asynchronous disk writes are completed, regardless of whether you made any new changes or not.

Relevant log output

@NathanielJS1541 NathanielJS1541 added the t/bug Something isn't working label Feb 5, 2025
@jsuarezruiz jsuarezruiz added platform/android 🤖 area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info labels Feb 6, 2025
@Zhanglirong-Winnie Zhanglirong-Winnie added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Feb 7, 2025
@Marnie-Majait
Copy link

Marnie-Majait commented Feb 7, 2025

This issue has been verified Visual Studio 17.13 Preview 5.0 (9.0.30 & 9.0.22 & 9.0.0 & 8.0.100, 8.0.93, 8.0.3). Can repro this issue on Android platform.

@bricefriha
Copy link

bricefriha commented Feb 7, 2025

@NathanielJS1541 In your particular case. Are you trying to purposefully store the data before terminating the app? Or do you just happen to want to exit the app while a disk is being written?

If it's the former, another solution would be to add something like a Preferences.SaveAndExit() that would write the data and then exit the app, ensuring it is stored before the app terminates.

Edit: now that I think about it, a Preferences.Commit() would be better actually

@NathanielJS1541
Copy link
Author

@bricefriha in my particular case, the save and then quit is intentional. If the user presses the back button twice on our home screen the app will quit. When this happens, we want to ensure that some preferences have been saved, so we make calls to set all changed preferences before calling Application.Quit().

This might be a bit of a niche use-case, but I'm sure there are other scenarios where you might run into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info platform/android 🤖 s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Projects
None yet
5 participants