Lesson Weekend

Now that we know how to retrieve data from a web service, let's learn how to save data from our application. There are many ways to persist data in Android. In this lesson we will learn how to save small bits of data directly into the user's phone with shared preferences. In subsequent lessons we'll explore saving larger portions of data to a web service backend called Firebase.

Shared preferences is an Android class that allows apps to store key-value pairs of primitive data types. Once saved, information in shared preferences will persist across sessions. It's important to note that despite the name, shared preferences are not strictly reserved for saving user preferences, options or settings. Also, any modifications to shared preferences must go through a dedicated tool called SharedPreferences.Editor. This is to ensure values remain consistent, and are saved correctly.

We will use shared preferences in our MyRestaurants application to save the zip code a user enters in our MainActivity. This will prevent users from having to enter a zip code each time they open our application. The data will persist in the phone's shared preferences until the user overrides it by manually entering a different zip code.

Saving to Shared Preferences

Let's begin integrating shared preferences into our MyRestaurants application. Remember, shared preferences may only store primitive data types in key-value pairs. We'll start by adding the following key to our Constants class:

Constants.java
public static final String PREFERENCES_LOCATION_KEY = "location";

The string "location" will act as the key in our key-value pair. We've placed this in our Constants file because no matter what zip code the user enters, we will always use the same key to access this value.

Next, we'll add the following code to our MainActivity:

MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private SharedPreferences mSharedPreferences;
    private SharedPreferences.Editor mEditor;

    @Bind(R.id.locationEditText) EditText mLocationEditText;
    @Bind(R.id.findRestaurantsButton) Button mFindRestaurantsButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        mEditor = mSharedPreferences.edit();

        mFindRestaurantsButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v == mFindRestaurantsButton) {
            String location = mLocationEditText.getText().toString();
            addToSharedPreferences(location);
            Intent intent = new Intent(MainActivity.this, RestaurantListActivity.class);
            startActivity(intent);
        }
    }

    private void addToSharedPreferences(String location) {
        mEditor.putString(Constants.PREFERENCES_LOCATION_KEY, location).apply();
    }
}

Let's go through this step by step:

  • First, we begin by creating member variables to store reference to the shared preferences tool itself (mSharedPreferences) and the dedicated tool we must use to edit them (mEditor).
  • Next, we create a method called addToSharedPreferences(), which takes the user-inputted zip code as an argument
  • addToSharedPreferences() calls upon the editor to write this information to shared preferences.
  • Then, since shared preference data must be in key-value pairs, we provide the editor the key we've stored in our Constants file called PREFERENCES_LOCATION_KEY, and the zip code value we've passed in as an argument, location.
  • Finally, we call apply() to save this information.

Testing Shared Preferences

To test that this is successfully saving information, let’s log the contents of our shared preferences to the logcat in our RestaurantListActivity:

RestaurantListActivity.java
public class RestaurantListActivity extends AppCompatActivity {
    private SharedPreferences mSharedPreferences;
    private String mRecentAddress;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

    ...

        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        mRecentAddress = mSharedPreferences.getString(Constants.PREFERENCES_LOCATION_KEY, null);
        Log.d("Shared Pref Location", mRecentAddress);
    }
    ...
}

Similar to the dedicated editor we must use to alter shared preferences, we also call the dedicated PreferenceManager to access them.

Here, we retrieve our shared preferences from the preference manager, pull data from it by calling getString() and providing the key that corresponds to the data we'd like to retrieve. We also pass in the default valuenull. The default value will be returned if the getString() method is unable to find a value that corresponds to the key we provided.

Let’s run our app, enter a zip code, and see if it prints to the logcat in RestaurantListActivity:

logging-shared-preferences-data-to-logcat-successfully

And look, there it is! We're successfully saving and retrieving data in Android's shared preferences!

Using Shared Preferences Data Automatically

Now that we can successfully save a user's zip code in shared preferences, let's program our application to automatically retrieve restaurant data from the Yelp API if a location has already been saved. After all, if we already know the user's location, we don't need to ask them to provide it again.

Note: Be mindful of storing zip code data in your shared prefs for your individual projects! Does it make sense for your app to do this? In many cases, users will explicitly want to be able to search for zipcodes other than their home zipcodes. Use SharedPrefs wisely and sparsely, and provide clear opportunities for your users to edit them!

We'll add the following code to our RestaurantListActivity, and remove our logcat line :

RestaurantListActivity.java
public class RestaurantListActivity extends AppCompatActivity {
    private SharedPreferences mSharedPreferences;
    private String mRecentAddress;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

    ...

        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        mRecentAddress = mSharedPreferences.getString(Constants.PREFERENCES_LOCATION_KEY, null);
        if (mRecentAddress != null) {
            getRestaurants(mRecentAddress);
        }
    }
    ...
}

Here we are defining mSharedPreferences as the shared preferences information we're using the preference manager to access, and mRecentAddress as the value we're attempting to retrieve. We then check if mRecentAddress does not equal null. If it does not, we know we have a zip code saved, and we pass that zip code to our getRestaurants() method. As we know, the getRestaurants() method then calls the Yelp API and returns restaurants near that location.

Also, we only want to save the location to shared preferences if the user has actually entered something into the form field. We'll add the following conditional to instruct our app to only save locations into shared preferences if they are not an empty string:

MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    ...

    @Override
    public void onClick(View v) {
        if (v == mFindRestaurantsButton) {
            String location = mLocationEditText.getText().toString();
            if(!(location).equals("")) {
                addToSharedPreferences(location);
            }
            Intent intent = new Intent(MainActivity.this, RestaurantListActivity.class);
            startActivity(intent);
        }
    }

    ...

Let’s run the app again. We do still need to hit the "Find Restaurants" button to view restaurants, but we may now leave the form field blank and the ListView in the RestaurantListActivity will automatically populate with restaurants near our saved location.

Important Note: If you feel like submitting a blank form to access the saved location is a little counter-intuitive and clunky, you're entirely correct. Know that this is only temporary! In future lessons we'll add features that circumvent the need to do this. But for now, if you can hit "Find Restaurants" without entering a zip code and receive the list of restaurants in the zip code you last searched, your shared preferences are working!


Example GitHub Repo for MyRestaurants

Terminology


  • Shared preferences: An Android class that allows apps to store key-value pairs of primitive data types. Once saved, information in shared preferences will persist across sessions.

Tips


  • Any modifications to shared preferences must be made through a dedicated tool called SharedPreferences.Editor.

  • Shared preferences may only store primitive data types in key-value pairs.

Examples


Saving to Shared Preferences

private SharedPreferences mSharedPreferences;
private SharedPreferences.Editor mEditor;
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mEditor = mSharedPreferences.edit();
mEditor.putString(Constants.PREFERENCES_LOCATION_KEY, location).apply();

Accessing Data from Shared Preferences

private SharedPreferences mSharedPreferences;
private String mRecentAddress;
 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 mRecentAddress = mSharedPreferences.getString(Constants.PREFERENCES_LOCATION_KEY, null);

Example GitHub Repo for MyRestaurants

Additional Resources