Chapters

Hide chapters

Android Apprentice

Third Edition · Android 10 · Kotlin 1.3 · Android Studio 3.6

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section III: Creating Map-Based Apps

Section 3: 7 chapters
Show chapters Hide chapters

8. SharedPreferences
Written by Darryl Bayliss

In the previous chapter, you set up an Activity to use a RecyclerView.

In this chapter, you’ll update Listmaker to create, save, and delete lists. You’ll show these lists in the RecyclerView created in the last chapter. You’ll also learn about a new topic called SharedPreferences, a simple way to save data about your app.

By the end of the chapter, you’ll know:

  • What SharedPreferences are
  • How to use SharedPreferences to save and retrieve objects

Getting started

If you’re following along with your own project, open it and keep using it with this chapter. If not, don’t worry. Locate the projects folder for this chapter and open the Listmaker app inside the starter folder.

The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.

With the Listmaker project open in Android Studio, run the project using a device or emulator.

Notice the round turqoise button in the bottom right? That’s called a Floating Action Button, better known as a FAB. You use a FAB to highlight an important action on the screen.

Creating lists is the most important action in Listmaker, so it makes sense to use a FAB to add new lists. The icon in the FAB doesn’t convey well the action of adding a new list, so your first task is to select an appropriate icon.

Open activity_main.xml, and in the Component Tree window, select the Floating Action Button.

In the Attributes window on the right-hand side of Android Studio, locate the srcCompat text field. This is where you assign the image to the button. Currently, it has a value of @android:drawable/ic_dialog_email.

Change the value in srcCompat to @android:drawable/ic_menu_add and press Enter. The image in the FAB changes to a more appropriate plus sign icon.

With the icon changed, Users can better understand the purpose of this button. In the next part, you’ll add the code that allows users to create a new list.

Adding a Dialog

When users tap the FAB in Listmaker, you want the button to open a Dialog where they can enter a name for their new list. The Dialog will contain labels to prompt users for information.

Rather than hardcoding these prompt strings, you’ll add these strings to strings.xml. This keeps the strings for Listmaker in one place, making it easier to update the strings or to support another language in the future.

Open strings.xml and add the following strings:

<string name="name_of_list">What is the name of your list?</string>
<string name="create_list">Create</string>

Open MainActivity.kt. At the bottom of the file, add a method to create an AlertDialog to get the name of the list from the user:

private fun showCreateListDialog() {
  // 1
  val dialogTitle = getString(R.string.name_of_list)
  val positiveButtonTitle = getString(R.string.create_list)

  // 2
  val builder = AlertDialog.Builder(this)
  val listTitleEditText = EditText(this)
  listTitleEditText.inputType = InputType.TYPE_CLASS_TEXT

  builder.setTitle(dialogTitle)
  builder.setView(listTitleEditText)

  // 3
  builder.setPositiveButton(positiveButtonTitle) { dialog, _ ->
    dialog.dismiss()
  }

  // 4
  builder.create().show()
}

With this method, you:

  1. Retrieve the strings you defined in strings.xml for use in the Dialog.

  2. Create an AlertDialog.Builder to help construct the Dialog. An EditText View is created as well to serve as the input field for the user to enter the name of the list.

    The inputType of the EditText is set to TYPE_CLASS_TEXT. Specifying the input type gives Android a hint as to what the most appropriate keyboard to show is. In this case, a text-based keyboard, since you want the list to have a name.

    The title of the Dialog is set by calling setTitle. You also set the content View of the Dialog. In this case the EditText View, by calling setView.

  3. Add a positive button to the Dialog; this tells the Dialog a positive action has occurred and something should happen.

    You pass in positiveButtonTitle as the label for the button and implement an onClickListener. For now, you dismiss the Dialog. You’ll handle the resulting actions behind the button in the next section.

  4. Finally, you instruct the Dialog Builder to create the Dialog and display it on the screen.

Now that you have code to show the Dialog, you need to call it when the user taps the FAB. Locate the setOnClickListener called on fab inside onCreate. Replace the contents of the OnClickListener with a call to the new method:

fab.setOnClickListener {
  showCreateListDialog()
}

Run the app and tap on the pink FAB in the bottom-right of the screen. You’ll see the Create List Dialog appear as expected.

Try typing in a name for the list, then click Create. Nothing will happen, but that’s okay. Next, you need to add code to handle the creation of the list inside the onClickListener of the positive button for the Dialog.

You’ll do this later on in the chapter. For now though, you’ve done everything you can here. Your next task is to define what a list looks like in Listmaker. You’ll do that in the next part.

Creating a list

Start by creating a model for a list to use throughout the app.

In the Project navigator, Right-click com.raywenderlich.listmaker. In the options that appear, select New ▸ Kotlin File/Class:

In the popup window that appears, name the new Kotlin file TaskList, then change the kind to Class and press enter.

Android Studio creates and displays the new class. Next, add a primary constructor to TaskList.kt so it can be given a name and a list of associated tasks:

class TaskList(val name: String, val tasks: ArrayList<String> = ArrayList()) {

}

Next, you need a way to save the list to the device. You can do this by using SharedPreferences.

SharedPreferences allows you to save key-value pairs to a device, that you can retrieve later. If you need a way to save small sets of data in your app quickly, you should consider using SharedPreferences.

Behind the scenes, SharedPreferences writes key-value pairs to a single file. You can configure it to write to multiple files for more complex apps. You can also allow other apps to access your apps’ SharedPreferences store if you think other apps have a valid reason to access your data.

Note: SharedPreferences is a quick way to persist and retrieve data. However, it isn’t perfect.

SharedPreferences only supports saving simple properties and a hashset. This chapter uses the hashset to store lists, which doesn’t store the lists in order. You may notice your lists appearing in different orders as you use the app.

There are better alternatives to SharedPreferences when you have complex data needs, which you’ll learn about in later chapters.

To use SharedPreferences, you need to add a dependency to your project. A dependency is some code that helps with a particular problem, and saves you the time and effort from having to write the code yourself.

In the project navigator, open the build.gradle (Module: app) file. Take a moment to look at the dependencies block.

These are the dependencies Listmaker is using. You may notice ConstraintLayout is listed as a dependency, and a few others you may not understand.

What each of them are doesn’t matter. For now, in the dependencies block, add the following line to inform your app you want to use the preference library:

implementation 'androidx.preference:preference:1.1.0'

At the top of the file, a message will appear. Informing you the file has changed.

Click the Sync Now button to the right. Android Studio begins to download the dependency and make sure it’s available to you.

With SharedPreferences setup, it’s time to use it. You need a class to manage the lists Listmaker creates. Create a new class and name it ListDataManager, then create the primary constructor for the new class as follows:

class ListDataManager(private val context: Context) {
  fun saveList(list: TaskList) {
    // 1
    val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context).edit()
    // 2
    sharedPreferences.putStringSet(list.name, list.tasks.toHashSet())
    // 3
    sharedPreferences.apply()
  }
}

You pass a Context into ListDataManager and add a method named saveList(list: TaskList) to persist the list to sharedPreferences. Here’s what’s going on, step by step:

  1. Get a reference to the app’s default SharedPreference store via PreferenceManager.getDefaultSharedPreferences(context). With the PreferenceManager object it returns, append .edit() to it to get a SharedPreferences.Editor instance. This allows you to write key-value pairs to SharedPreferences.

  2. Write TaskList to SharedPreferences as a set of Strings, passing in the name of the list as the key. Since the tasks in TaskList is an array of strings, you can’t store it directly in a string, so you convert the tasks in TaskList to a HashSet which SharedPreferences can use as a value to save. Since HashSet is a Set, it ensures unique values in the list.

  3. Instruct the SharedPreferences Editor instance to apply the changes. This writes the changes to Listmaker’s SharedPreferences file.

That takes care of saving lists. You also need a way to retrieve lists from SharedPreferences. To do that, add a new method called readLists() following below saveList:

fun readLists(): ArrayList<TaskList> {
  // 1
  val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
  // 2
  val sharedPreferenceContents = sharedPreferences.all
  // 3
  val taskLists = ArrayList<TaskList>()

  // 4
  for (taskList in sharedPreferenceContents) {

    val itemsHashSet = ArrayList(taskList.value as HashSet<String>)
    val list = TaskList(taskList.key, itemsHashSet)
    // 5
    taskLists.add(list)
  }

  // 6
  return taskLists
}

Going through this code step-by-step:

  1. Grab a reference to the default SharedPreferences file. This time, you don’t request a SharedPreferences.Editor since you only need to read from SharedPreferences, not write to it.

  2. Call sharedPreferences.all to get the contents of the SharedPreferences file as a Map.

    Note: A Map is a collection that holds pairs of objects (keys and values) and supports retrieving the value corresponding to each key. Map keys are unique; a map holds only one value for each key. Note that in some other programming languages, this type of data structure is called a Dictionary.

  3. Create an empty ArrayList of type TaskList. You’ll use this to store the lists you retrieve from SharedPreferences.

  4. Iterate over the items in the Map you received from SharedPreferences using a for loop. For each iteration, take the value of the object and attempt to cast it to a HashSet<String>. Recall from SaveList() that you couldn’t store a TaskList directly as a string, so you converted the list of tasks into a HashSet. You perform the reverse of this to retrieve the tasks and convert them back to an ArrayList of strings. Then, recreate the TaskList by passing the key of the MapEntry as the name of the TaskList and the ArrayList of strings as the tasks.

  5. Finally, add the newly reconstructed TaskList into the empty ArrayList you created earlier.

  6. Once you iterate over the entire set of items you retrieved from SharedPreferences, return the contents of taskLists to the caller of the method.

Hooking up the Activity

In the previous section, you created ListDataManager to read and write the lists Listmaker creates. In this section, you’ll begin to use it.

Open MainActivity.kt and initialize a property to hold the ListDataManager:

val listDataManager: ListDataManager = ListDataManager(this)

This creates a new ListDataManager as soon as the Activity is created.

Next, update the positive button’s onClickListener in showCreateListDialog() to add create a list and save it to the ListDataManager:

builder.setPositiveButton(positiveButtonTitle) { dialog, _ ->
  val list = TaskList(listTitleEditText.text.toString())
  listDataManager.saveList(list)

  val recyclerAdapter = listsRecyclerView.adapter as ListSelectionRecyclerViewAdapter
  recyclerAdapter.addList(list)

  dialog.dismiss()
}

You take the name of the list and create an empty TaskList to save to SharedPreferences. You then get the adapter of the RecyclerView and cast it as the custom adapter ListSelectionRecyclerViewAdapter created earlier.

Using the adapter, you pass the TaskList into the adapter using addList, so it knows it has something to show. Don’t worry about the Unresolved reference error on addList; you’ll create this method shortly.

That’s the background work done for this feature. Next, you need to let the RecyclerView and its Adapter know about the new datasource.

In the onCreate method of MainActivity.kt, replace the set up code for the RecyclerView starting with:

// 1
val lists = listDataManager.readLists()
listsRecyclerView = findViewById<RecyclerView>(R.id.lists_recyclerview)
listsRecyclerView.layoutManager = LinearLayoutManager(this)

// 2
listsRecyclerView.adapter = ListSelectionRecyclerViewAdapter(lists)

Going through the code step by step:

  1. You get a list of TaskLists from listDataManager, ready for use.
  2. Remember that static array of list titles you added earlier? You’re beginning to replace that with the list of TaskLists your app stores. Ignore the Too many arguments error, you’re going to update ListSelectionRecyclerViewAdapter to accept a parameter.

Now the RecyclerView Adapter has a source of information to display; there are a few changes you need to make to ensure everything works with the new lists.

Open ListSelectionRecyclerViewAdapter.kt and update the class definition to accept an ArrayList of TaskList in its primary constructor:

class ListSelectionRecyclerViewAdapter(private val lists : ArrayList<TaskList>) : RecyclerView.Adapter<ListSelectionViewHolder>() {

Find onBindViewHolder() and update it to use the list to populate the ViewHolder instead of the static array of strings:

override fun onBindViewHolder(holder: ListSelectionViewHolder, position: Int) {
  holder.listPosition.text = (position + 1).toString()
  holder.listTitle.text = lists.get(position).name
}

Modify getItemCount() to get the size of lists:

override fun getItemCount(): Int {
   return lists.size
}

Finally, create the addList() method you called from MainActivity to let the adapter know you have a new list to display. Add the following code to the bottom of the Adapter class:

fun addList(list: TaskList) {
  // 1
  lists.add(list)

  // 2
  notifyItemInserted(lists.size-1)
}

Here’s what’s happening:

  1. You update the ArrayList with the new TaskList.

  2. You call notifyItemInserted() to inform the Adapter that you updated the data source, and you update the RecyclerView. In this case, the data source is the ArrayList passed into the ListSelectionRecyclerViewAdapter, and any necessary ViewHolders are created to populate each View with the right data for each position.

With that done, remove the listTitles array at the top of the ListSelectionRecyclerViewAdapter since you no longer need it.

Run the app, tap the FAB to display the Create List Dialog and give the list a name.

Tap Create and the new task list appears in the RecyclerView.

You’re not quite done — there’s one thing left to verify. Does the list stick around after you stop and restart the app?

Click the Stop button in Android Studio; it’s the big red square in the toolbar at the top.

Your device stops running the app and goes back to the home screen. Once again, run the app from Android Studio and the list returns.

With this test done, you can be certain the app persists the list to SharedPreferences and loads it when relaunched. Great job!

Note: Just to remind you, you may notice the order of the list titles changing as the app relaunches. This highlights one of the issues when using SharedPreferences.

SharedPreferences is only a key-value store; it doesn’t order your data.

For this example, SharedPreferences is a great option to store and read data quickly. However, as your needs become more complex, you should consider other methods of storage that adhere to ordering; these are explained later in the book.

Where to go from here?

SharedPreferences is the simplest way to persist values in an Android app, so it’s worth keeping in your toolbox.

In this chapter, you learned how to write and read values from SharedPreferences and put that knowledge to good use in Listmaker — like letting your users save and load their lists! The next logical step is to let users add items to their lists, which is exactly what you’ll do in the next chapter!

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.
© 2026 Kodeco Inc.