Home Android & Kotlin Books Saving Data on Android

2
Using SharedPreferences Written by Fuad Kamal

Files are a quick and convenient way to store unstructured data in Android. But there are other convenient, more organized ways to store small bits of data. One of them is by using SharedPreferences.

Understanding SharedPreferences

The SharedPreferences, or prefs for short, API provides the means to read and write to a file that is formatted as XML. The API is especially useful when you need to store things like application-wide flags to style your app, user progress, data filters and so on.

The prefs file you create, edit and can delete is stored in the data/data/{application package name} directory. The location of this file can be obtained programmatically by calling Environment.getDataDirectory(). Like files, the data is app—specific and will be lost if the application data is cleared through settings or if the app is uninstalled.

You already learned that SharedPreferences should not be used to store large amounts of data, but rather small features of your app. As such, the data is structured into key—value pairs, where each value has an identifier key, which marks its location in the file. Then, by passing in the key, you can retrieve the last value you stored. If there is no value for the key, you can specify a default that will be returned instead. Furthermore, since the point of SharedPreferences is to store simple data, the only supported types to store are Boolean, Int, Float, Long, String and Set<String>.

Note: SharedPreferences are also stored at the location where app Preferences are. However, the two should not be confused with one another.

The first step to using SharedPreferences is obtaining a reference to the app—specific file, let’s see how to do that!

Getting a reference to the SharedPreferences file

Depending on how you want to use SharedPreferences, there are different ways to access or create the SharedPreferences file:

  • getSharedPreferences() can be called from any Context in your app and allows you to specify a name for the file. It’s handy if you need multiple SharedPreferences files with different names.
  • getPreferences() is called from Activity and doesn’t allow you to specify a file name because it uses the default class name of that Activity. It’s handy if you need to create different preference files for different activities.
  • getDefaultSharedPreferences() is used for app settings and preferences at the app level, and returns the default prefs file for the entire app. You can use this to store data that is useful for your entire app, or features you set up every time a user starts your app.

But getting preferences is only half of the work. You still need to read and write data to the prefs.

Reading from preferences

Reading from prefs is pretty straightforward. Once you get ahold of a preference file, you can use aptly named functions to read the variables of the data types mentioned earlier in this chapter. You can use functions such as getInt(), getString() and getLong(), passing in a key and an optional default value to get the stored value. An example call, to get a String, by the key username, with a default value of an empty String is as follows:

prefs.getString("username", "")

You will learn more about reading data through the chapter’s project, but for now, let’s see how to write some data to SharedPreferences.

Writing to preferences

Writing to the SharedPreferences file is slightly more complicated. To write to the file you must open it for editing, by creating an Editor. You can do that using edit() on your SharedPreferences. The SharedPreferences.Editor is essentially a pointer to the SharedPreferences file, in the app’s data directory. Then you pass key—value pairs to methods such as putInt(), putString() and putLong(). Once the key—value pairs have been added to the Editor, call either apply() or commit() to finalize the changes, and save to the file.

Generally, it’s a good practice to choose apply() to write the SharedPreferences. apply() will write the changes to the object out immediately, but then saves those changes to the disk asynchronously. If two Editors try to use apply() at the same time, the last one to call the function will be the one that has its changes saved. apply() will complete before the app switches state so you don’t have to worry about it interfering with the lifecycle of your app. commit() writes the changes synchronously which can block the main thread and result in the app locking up, until everything is properly stored.

Now, it’s time to look at an example of SharedPreferences in action.

Getting started

To get started with prefs, locate this chapter’s folder in the provided materials named using-sharedpreferences, and open up the projects folder. Next, open the organizedsimplenotes app under the starter folder. Allow the project to sync, download dependencies and set up the workplace environment. Run the app on a device or in a simulator. For now, ignore any warnings in the code.

The OrganizedSimpleNote App Interface.
The OrganizedSimpleNote App Interface.

The app allows you to create, edit and delete notes that are saved in the internal file system. In the options menu items, there is an option to change the background color. You can also filter the notes by priority, or sort them by specific sort order.

The Option Menu Items.
The Option Menu Items.

Select a new background color, sort order and one or more priority filters. Now, quit the app and rerun it.

When the app reloads, the background color you selected persists. That is because the background color you selected was saved to prefs and applied when the app was rerun. The sort order and priority filters reset to defaults. They were not stored in prefs.

Changed Background Color persist.
Changed Background Color persist.

To see the saved value for the background color, open the Device File Explorer in Android Studio, as you did in the previous chapter and navigate to the data/data/com.raywenderlich.organizedsimplenote/shared_prefs directory. You’ll see a file named com.raywenderlich.organizedsimplenote_preferences.xml.

The SimpleNote App Interface
The SimpleNote App Interface

If you open the file, you’ll see something similar to the following, depending on the color you chose:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
  <string name="key_app_background_color">Orange</string>
</map>

In the next steps, you’ll add the code to save and retrieve the sort order and priority filters to and from the prefs so that these settings can persist between runs of the app along with the background color.

Saving the user preferences

First, you have to create some constants which will represent the keys for the key—value pairs of data you need to save. Open NotePrefs.kt and insert the following declarations right below the declaration of DEFAULT_PRIORITY_FILTER:

  private const val KEY_NOTE_SORT_PREFERENCE = "note_sort_preference"
  private const val KEY_NOTE_PRIORITY_SET = "note_priority_set"

Next, you have to create the function to write the sort order to the prefs file. Insert the code below into saveNoteSortOrder(), replacing TODO:

sharedPrefs.edit()
    .putString(KEY_NOTE_SORT_PREFERENCE, noteSortOrder.name)
    .apply()

This code retrieves the Editor object from the app’s prefs and puts the sort order in the preferences as a String with the key KEY_NOTE_SORT_PREFERENCE. It then uses apply() so that the changes will be written asynchronously to the disk.

Now, provide the code to write the selected priority filters. Insert the code below into saveNotePriorityFilters(), once again replacing TODO:

sharedPrefs.edit()
    .putStringSet(KEY_NOTE_PRIORITY_SET, priorities)
    .apply()

The previous code puts priorities, a Set of Strings, representing the selected priority filters, into the prefs using putStringSet() with the key KEY_NOTE_PRIORITY_SET.

Awesome! The functions to write to the prefs are done. Next, you’ll add the code to read the values.

Reading the user preferences

Currently, getNoteSortOrder() returns the default value of NoteSortOrder.FILENAME_ASC. Replace the returned value with the following:

NoteSortOrder.valueOf(
  sharedPrefs.getString(KEY_NOTE_SORT_PREFERENCE, DEFAULT_SORT_ORDER)
      ?: DEFAULT_SORT_ORDER
)

The above code uses getString() to retrieve the sort order the user stored, or DEFAULT_SORT_ORDER as the default value if the key KEY_NOTE_SORT_PREFERENCE doesn’t exist.

Next, write the function to read the priority filters. Right now getNotePriorityFilters() returns an empty Set. Replace setOf() with the following:

sharedPrefs.getStringSet(KEY_NOTE_PRIORITY_SET, setOf(DEFAULT_PRIORITY_FILTER))
      ?: setOf(DEFAULT_PRIORITY_FILTER)

The code above reads a Set of Strings from the prefs with the key KEY_NOTE_PRIORITY_SET, and if nothing is found with that key, it will use DEFAULT_PRIORITY_FILTER as the default - a set with the priority of "1" inside.

Now that the functions to read and write the sort order and priority filters are written, you can use these functions in MainActivity.

Reading and writing the prefs from MainActivity

Right now, in MainActivity.kt, you’re using hardcoded values for the sort order and the priority filters, which you pass on to NoteAdapter, to display notes in a different order and filter which notes should be in the list. The right way to do this, which involves SharedPreferences, is to read a user’s preferred ways of sorting and filtering, from prefs, and then passing those values to NoteAdapter.

To do this, open MainActivity.kt if you didn’t already, and change the way you create the priorities Set by changing the statement to the following:

private val priorities by lazy { notePrefs.getNotePriorityFilters().toMutableSet() }

Instead of creating an empty mutable set and then adding values to it, you’re now reading from the preferences, in a lazy way. Once you create NoteAdapter, the value will be assigned and you can use priorities.

Then, change the creation of the NoteAdapter:

private val noteAdapter: NoteAdapter by lazy {
  NoteAdapter(this,
      priorities,
      notePrefs.getNoteSortOrder(), // Read from preferences
      ::showEditNoteDialog
  )
}

Instead of passing in a hardcoded value for FILENAME_ASC as the NoteSortOrder, you’re reading from the preferences to get the ordering the user previously selected.

Note: Do not attempt to modify a string set read from the prefs. The results can be unpredictable. Always make a copy of the set instead, if it is going to be modified, like in the case of this app. You can do that by calling toMutableSet(), because Kotlin internally creates a new object, instead of just copying the Set value.

The app can now load the saved preferences when the app launches, but it also needs to store the values anytime the menu options change. To do that, find updateNoteSortOrder() and insert the following, replacing TODO:

notePrefs.saveNoteSortOrder(sortOrder)

The line of code above will save the sortOrder to the prefs. Finally, find updateNotePrioritiesFilter() at the bottom of MainActivity and insert this line of code, once again replacing TODO:

notePrefs.saveNotePriorityFilters(priorities)

The above statement will save the list of priority filters to the prefs. If you look in onOptionsItemSelected(), the above two functions you just wrote the code for are called anytime the appropriate menu options are changed.

Finally, build and run. You can now filter and sort notes to your heart’s content and the app will save your personalized preferences. Once you come back, everything will be ready and waiting for you!

SharedPreferences Saving Successful.
SharedPreferences Saving Successful.

Key points

  • SharedPreferences are a great way to store small bits of data for an app.
  • User—specific app configuration data is often stored in SharedPreferences.
  • Preferences are also stored in an app’s default SharedPreferences, but they are not to be confused with each other.
  • SharedPreferences are not a good choice to store structured data, like various entities, use a database for that.
  • SharedPreferences are organized in key—value pairs that are ultimately written out to a file in .xml format.
  • Each app has its own SharedPreferences. When the app is uninstalled or the data is deleted, these stored values are wiped away.
  • You can create custom-named SharedPreferences files and store user’s choices in them.
  • To edit SharedPreferences, you first have to fetch its Editor, by calling edit().
  • When writing SharedPreferences, use apply() instead of commit() to perform an asynchronous, thread—safe write.

Where to go from here?

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC