When does Android's SharedPreferences commit() return false?-ThrowExceptions

Exception or error:

In my app, I’m storing some data in SharedPreferences – it works as needed.
Now I want to create log messages for some incorrect situations.

I successfully handle situations where the preferences are empty or they throw some exception while loading.
The only error that can show up while saving values is commit() returning false – I don’t know why this could happen and what I should do in that case.

So my question is:
When does the commit() method return false?
And what should I do in that case?
Could a possible solution be to call commit() again and again (something like while (!editor.commit()) {})?

How to solve:

First, you would never want to do:
while(!editor.commit()) {;}. If you want to retry a commit you would do so a handful of times rather than loop forever.

Regarding when commit() returns -1:
Both commit() and apply() make the changes to memory and then make the calls to write the changes to disk (per source). commit() waits for the disk write to return to return the results. So the answer to your question is: “When saving the preference state to disk fails for your particular commit() call.” Since changes were reflected in memory they may or may not be written to disk by another commit() or apply() call before or after your attempted disk write (think racing threads). Since there isn’t code to rollback the memory update and SharedPreferences are singletons (per source) other components in your app will see your changes even if they didn’t save to disk.

In summary, looping a few times (not forever) to ensure a commit() writes to disk seems fine to get you past an intermitten I/O issue, but it would be a rare I/O-based situation that it fails. If you need perfect commit scope you would want to re-read the disk to validate each commit() call which has performance implications and is overkill for all but a small set of situations.

###

And can while(!editor.commit()) {;} be solution for success anyway?

‘commit’ operation is synchronous and performed on the UI thread, thus, if you have some unrecoverable error which occurs every time you try to perform commit, you’ll block your entire UI thread with this code and user will see ANR. Not good.
‘apply’, on the other hand, is asynchronous, thus it can’t block UI thread even if you will perform it indefinitely long.
Documentation says very little about the possible failures of the ‘commit’ operation (take a look here here, here and there). The preferences are stored inside the xml file stored on your internal storage, so one possible failure can be the corrupted xml file. The second one can be found in documentation:

Note that when two editors are modifying preferences at the same
time, the last one to call commit wins.

There is little you can do about the corrupted file, and, well, don’t try to modify preferences from different places at the same time. 🙂

###

commit() can return false if there’s a problem in saving your data.

Look at the difference between commit() and apply() to get a clearer idea (taken from this site)

apply()

This saves your data into memory immediately and saves the data to
disk on a separate thread. So there is no chance of blocking the main
thread (your app won’t hang).

It is the preferred technique but has only been available since
Gingerbread (API 9, Android 2.3).

commit()

Calling this will save the data to the file however, the process is
carried out in the thread that called it, stopping everything else
until the save is complete. It returns true on successful completion,
false on failure.

Use commit() if you need confirmation of the success of saving your
data or if you are developing for pre-Gingerbread devices. commit()
has been available since API 1

###

I would be approaching this problem from another angle altogether.

There are a couple of alternate ways to deal with this.

What I do is when my program first runs, I
initialise my shared preference value to a test value for me to check if it
has been changed.

eg:

editor.putInt("key", -1);

Then in the activity where I am retrieving the value of the preference:

int mySavedInt = this.pref.getInt("key", howIgetMyInt)
if(mySavedInt==-1){
    // I know it's not my saved int :) and hasn't been set.
}

Or to actually use a shared preference itself to determine whether the shared preferences
you are attempting to validate have been updated.

eg.

String getStatus = pref.getString("checkMySharedPreferences", "nil");
    if (getStatus.equals("true")) {
        // DO something.
    } else {
        Toast.makeText(this, "Set your preferences",
                Toast.LENGTH_SHORT).show();
    }

Then when you set your shared preferences you can set this value to “true”:

editor.putString("checkMySharedPreferences", "true");
editor.commit();

Another suggestion is to ensure you validate any data before updating your shared preferences.
That way, the shared preferences will not be set if the data is not valid.

if(my data is valid){
    my shared preferences
    commit
}

The final suggestion is to do a cyclic check (as you are insecure in the process) and when you get your data for the shared preferences and commit it. Check that the user input equals the new shared preferences. Though I don’t see this as necessary.

And to log these events, just add you Log lines where applicable.

Also, just a side note, I don’t think it’s necessarily good practice to throw an exception if they are not set, I believe managing the program flow can be better handled with conditional statements where it is obvious there will be a runtime error. Save the error handling in validating the user input.

Leave a Reply

Your email address will not be published. Required fields are marked *