Home Android & Kotlin Books Android Apprentice

8
SharedPreferences & ViewModels Written by Darryl Bayliss

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the previous chapter, you set up a RecyclerView. In this chapter, you’ll update Listmaker to create, save, and delete lists. You’ll also learn about two new topics. SharedPreferences and ViewModels. SharedPreferences are a simple way to save data in your app, whilst ViewModels provide a way to manage data shown on screen in a way that respects the lifecycle of your app.

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

  1. What SharedPreferences are.
  2. How to use SharedPreferences to save and retrieve objects.
  3. What ViewModels are and how to use them in your apps.

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.

In this chapter, you want to begin creating lists. A good way to do that is by providing a button for your users. You’re going to add a particular button called a Floating Action Button, better known as a FAB. You use a FAB to highlight an important action on the screen, it’s part of a design language called Material Design. Don’t worry if you don’t know what this is, you’ll learn more about Material Design in chapter 12.

Open main_activity.xml, and in the Palette window, select Buttons.

Click and drag a FloatingActionButton onto the layout. A new window will appear, asking you to pick a Resource. A resource, in this case, is the image you want to show on your button.

In the search textfield along the top, type ic_menu_add. As you type, the resources will filter the images with the name in the textfield. One image will be left.

Click the image, then click OK in the bottom right. The FAB will appear in the layout.

It’s going to be hard for users to reach that button in the top left corner, so let’s move it to the bottom right of the screen. In the Attributes window, scroll all the way down to layout_gravity field and select the bottom and right checkboxes. Finally, set the layout_marginBottom and layout_marginRight to 8dp. This gives the button some space away from the edge of the screen.

Finally, change the id of the FAB to fabButton and you have your FAB all setup. In the next section, you’ll put it to use.

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. A dialog is a small window that appears over the screen, to inform the user about something and maybe even prompt them for information. Your Dialog will contain labels to prompt users for information.

<string name="name_of_list">What is the name of your list?</string>
<string name="create_list">Create</string>
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()
}
   private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  binding = MainActivityBinding.inflate(layoutInflater)
  val view = binding.root
  setContentView(view)
        
  if (savedInstanceState == null) {
    supportFragmentManager.beginTransaction()
      .replace(R.id.container, MainFragment.newInstance())
      .commitNow()
  }
        
  binding.fabButton.setOnClickListener {
    showCreateListDialog()
  }
}

Creating a list

Start by creating a new package in your project. This package will hold your data models for the app.

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

}

implementation 'androidx.preference:preference-ktx:1.1.1'

ViewModels

ViewModels in Android serve one purpose. To manage the data that’s shown on screen in a way that respects the lifecycle of your app. What does that mean?

// 1
class MainViewModel(private val sharedPreferences: SharedPreferences) : ViewModel() {

    // 2
    lateinit var onListAdded: (() -> Unit)

    // 3
    val lists: MutableList<TaskList> by lazy {
        retrieveLists()
    }

    // 4
    private fun retrieveLists(): MutableList<TaskList> {

        val sharedPreferencesContents = sharedPreferences.all
        val taskLists = ArrayList<TaskList>()

        for (taskList in sharedPreferencesContents) {
            val itemsHashSet = ArrayList(taskList.value as HashSet<String>)
            val list = TaskList(taskList.key, itemsHashSet)
            taskLists.add(list)
        }

        return taskLists
    }

    // 5
    fun saveList(list: TaskList) {
      sharedPreferences.edit().putStringSet(list.name, list.tasks.toHashSet()).apply()
      lists.add(list)
      onListAdded.invoke()
    }
}

Hooking up the UI to the ViewModel

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

private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  viewModel = ViewModelProvider(this,
    MainViewModelFactory(PreferenceManager.getDefaultSharedPreferences(this)))
    .get(MainViewModel::class.java)

    ... rest of the method code below ...
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
  super.onActivityCreated(savedInstanceState)
  viewModel = ViewModelProvider(requireActivity(),
            MainViewModelFactory(PreferenceManager.getDefaultSharedPreferences(requireActivity())))
            .get(MainViewModel::class.java)
}
// 1
class MainViewModelFactory(private val sharedPreferences: SharedPreferences) : ViewModelProvider.Factory {

    // 2
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(sharedPreferences) as T
    }
}

Showing Real Lists in the RecyclerView

Open MainActivity.kt. Then, in the positive button onClickListener for the AlertDialog. Add a new line underneath the dialog dismissal to create a new list in MainViewModel.

builder.setPositiveButton(positiveButtonTitle) { dialog, _ ->
  dialog.dismiss()
  viewModel.saveList(TaskList(listTitleEditText.text.toString()))
}
val recyclerViewAdapter = ListSelectionRecyclerViewAdapter(viewModel.lists)

binding.listsRecyclerview.adapter = recyclerViewAdapter

viewModel.onListAdded = {
  recyclerViewAdapter.listsUpdated()
}
class ListSelectionRecyclerViewAdapter(private val lists : MutableList<TaskList>) : RecyclerView.Adapter<ListSelectionViewHolder>() {
 override fun onBindViewHolder(holder: ListSelectionViewHolder, position: Int) {
  holder.binding.itemNumber.text = (position + 1).toString()
  holder.binding.itemString.text = lists[position].name
}
override fun getItemCount(): Int {
   return lists.size
}
fun listsUpdated() {
  notifyItemInserted(lists.size-1)
}

Key Points

Listmaker is beginning to look like a usable app and you’ve covered some important topics. You now know:

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. The next 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.

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

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.