[TUT] Backup & Restore SharedPreferences to the cloud

This tutorial will show you how you can backup and restore your users sharedpreferences into the cloud. This allows for your app to remember it’s settings in between device installs. This is based on the reference from Google here. In this example we have a simple activity that takes user input and displays it on the screen when a button is pressed. If you then uninstalled and reinstalled the app it would remember what was present on the screen and display it once more.

What we are going to do:

  1. Register with google for the Backup service
  2. Declare that our app uses this service
  3. Create some SharedPreferences that talk to the backup service
  4. Create an app that uses these SharedPreferences

Ok Here .. we .. go!

First things first Register for the backup service here. This gives you your backup API key that you need to use in your app. Just accept the T&C’s and enter the package name for your application. In this example it is ‘com.blundell.tut’.

We will be creating SharedPreferences so our preferences need a name. We declare this in our own constants file, keeping the name in once place is cleaner and stops silly spelling error bugs. It’s also better if you want to refactor later. So lets start with that. In this file we also have the name of any preferences we will be saving.

PreferenceConstants.java

package com.blundell.tut.persistance;

/**
 * Constants file so we don't get errors from simple spelling mistakes!
 * @author paul.blundell
 *
 */
public class PreferenceConstants {

	public static final String TUTORIAL_PREFERENCES = "TutorialPreferences";
	protected static final String NAME = "Name";
	protected static final String HELPER_KEY = "prefs";

}

Now that we have signed up for the Google backup service and have named our preferences, lets create the BackupAgent, this is how our apps SharedPreferences are given to the system which in turn saves them to the cloud. Luckily Google have written a convenience class that does all the hardwork for us. So if we extend this class, create a new helper giving it the name of our SharedPreferences thats good enough!

BlundellBackupHelper.java

package com.blundell.tut.persistance;

import android.app.backup.BackupAgentHelper;
import android.app.backup.SharedPreferencesBackupHelper;

/**
 * A backup agent is used by the Android system,
 * it asks your app "What preference files do you want to save to the cloud"
 * @author paul.blundell
 *
 */
public class BlundellBackupAgent extends BackupAgentHelper {

	@Override
	public void onCreate() {
		super.onCreate();
		// A Helper for our Preferences, this name is the same name we use when saving SharedPreferences
		SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PreferenceConstants.TUTORIAL_PREFERENCES);
		addHelper(PreferenceConstants.HELPER_KEY, helper);
	}
}

We declare this BackupAgent in our AndroidManifest in the application tag, this is how the system knows we intend to use backup and restore functionality. Also we add the api_key that we registered for at the start.

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blundell.tut"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <application
        android:backupAgent=".persistance.BlundellBackupAgent"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".ui.phone.MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="com.google.android.backup.api_key"
            android:value="AEdPqrEAAAAIrrT-fQHyP6Vk97giWeLRwXP9ML-wS1E6d8Kt7Q" />
    </application>

</manifest>

Alright! Now our preferences are being restored from the cloud magic! But we never save anything to the cloud .. yet. Android has another class called BackupManager, it has a method called datachanged(); when you call this method you are telling the system “my preferences have changed so save them to the cloud!”. So this is what we need to do.

The way this tutorial has implemented the BackupManager is to make it as re-useable as possible. If we take the BackupManager and the normal SharedPreferences, then wrap them in our own class we have complete control over when and how they are called.

Lets look at this in reverse. Here is our TutorialPreferences, it depends upon SharedPreferences and gives us a nice interface to save and retrieve data for our app. Note it has no idea we are saving to the cloud.

TutorialPreferences.java

package com.blundell.tut.persistance;

import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

/**
 * This class wraps a shared preferences,
 * doing this it allows us to use contextual method names when we save new preferences
 * @author paul.blundell
 *
 */
public class TutorialPreferences {

	private final SharedPreferences sharedPreferences;
	private final Editor editor;

	public TutorialPreferences(SharedPreferences sharedPreferences) {
		this.sharedPreferences = sharedPreferences;
		editor = sharedPreferences.edit();
	}

	public String getName(){
		return getString(PreferenceConstants.NAME);
	}

	// If we don't have the String preference - default is to return blank
	private String getString(String name) {
		return sharedPreferences.getString(name, "");
	}

	public void saveName(String name){
		saveString(PreferenceConstants.NAME, name);
	}

	// Always remember to call commit
	private boolean saveString(String key, String value) {
		return editor.putString(key, value).commit();
	}
}

Ok so TutorialPreferences depends on a SharedPreferences, so we need to give it one, but we still need to tell the BackupManager about changes to this class. Therefore if we create our own SharedPreferences we can do this. SharedPreferences and SharedPreferences.Editor are both interfaces, implementing these interfaces means we can act as a SharedPrefs. Then in the editor whenever the commit() method is called we can also tell the BackupManager this has happend. Follow? I hope so!

Check out our own versions of SharedPreferences and Editor:

CloudBackedEditor.java

package com.blundell.tut.persistance;

import java.util.Set;

import android.app.backup.BackupManager;
import android.content.SharedPreferences.Editor;

/**
 * This is our cloud editor, whenever one of the sharedpreferences is changed we inform the backup manager (saving to the cloud)
 * Doing this here allows us to keep the Activity clean and the Activity doesnt even need to know we are saving to the cloud!
 * @author paul.blundell
 *
 */
public class CloudBackedEditor implements Editor {

	private final Editor editor;
	private final BackupManager backupManager;

	public CloudBackedEditor(Editor editor, BackupManager backupManager) {
		this.editor = editor;
		this.backupManager = backupManager;
	}

	@Override
	public void apply() {
		throw new UnsupportedOperationException("Just a tutorial");
	}

	@Override
	public Editor clear() {
		editor.clear();
		return this;
	}

	@Override
	public boolean commit() {
		boolean commit = editor.commit();
		backupManager.dataChanged();
		return commit;
	}

	@Override
	public Editor putBoolean(String key, boolean value) {
		editor.putBoolean(key, value);
		return this;
	}

	@Override
	public Editor putFloat(String key, float value) {
		editor.putFloat(key, value);
		return this;
	}

	@Override
	public Editor putInt(String key, int value) {
		editor.putInt(key, value);
		return this;
	}

	@Override
	public Editor putLong(String key, long value) {
		editor.putLong(key, value);
		return this;
	}

	@Override
	public Editor putString(String key, String value) {
		editor.putString(key, value);
		return this;
	}

	@Override
	public Editor putStringSet(String arg0, Set<String> arg1) {
		throw new UnsupportedOperationException("Just a tutorial");
	}

	@Override
	public Editor remove(String key) {
		editor.remove(key);
		return this;
	}
}

CloudBackedSharedPreferences.java

package com.blundell.tut.persistance;

import java.util.Map;
import java.util.Set;

import android.app.backup.BackupManager;
import android.content.SharedPreferences;

/**
 * This class acts as shared preferences
 * When the editor is requested we send back our CloudEditor so that we have control of what is going on!
 * @author paul.blundell
 *
 */
public class CloudBackedSharedPreferences implements SharedPreferences {

	private final SharedPreferences sharedPreferences;
	private final BackupManager backupManager;

	public CloudBackedSharedPreferences(SharedPreferences sharedPreferences, BackupManager backupManager) {
		this.sharedPreferences = sharedPreferences;
		this.backupManager = backupManager;
	}

	@Override
	public boolean contains(String key) {
		return sharedPreferences.contains(key);
	}

	@Override
	public Editor edit() {
		return new CloudBackedEditor(sharedPreferences.edit(), backupManager);
	}

	@Override
	public Map<String, ?> getAll() {
		return sharedPreferences.getAll();
	}

	@Override
	public boolean getBoolean(String key, boolean defValue) {
		return sharedPreferences.getBoolean(key, defValue);
	}

	@Override
	public float getFloat(String key, float defValue) {
		return sharedPreferences.getFloat(key, defValue);
	}

	@Override
	public int getInt(String key, int defValue) {
		return sharedPreferences.getInt(key, defValue);
	}

	@Override
	public long getLong(String key, long defValue) {
		return sharedPreferences.getLong(key, defValue);
	}

	@Override
	public String getString(String key, String defValue) {
		return sharedPreferences.getString(key, defValue);
	}

	@Override
	public Set<String> getStringSet(String arg0, Set<String> arg1) {
		throw new UnsupportedOperationException("Just a tutorial");
	}

	@Override
	public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
		sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
	}

	@Override
	public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
		sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
	}
}

Thats it! Now we just have to wire up the Activity to use our SharedPreferences. Once this is done you can save anything you would normally save into shared preferences and it will be backed up to the cloud. When the app is uninstalled an reinstalled it will automatically restore these preferences with no further code.

Here is the Activity for the tutorial, it takes some user input then when the use hits the button saves it to the preferences and shows it on the screen. When the app starts it checks sharedpreferences and shows on the screen whatever was last saved.

MainActivity.java

package com.blundell.tut.ui.phone;

import com.blundell.tut.R;
import com.blundell.tut.persistance.CloudBackedSharedPreferences;
import com.blundell.tut.persistance.PreferenceConstants;
import com.blundell.tut.persistance.TutorialPreferences;

import android.app.Activity;
import android.app.backup.BackupManager;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity {

    private EditText inputEditText;
	private TextView displayTextView;
	private TutorialPreferences pref;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        inputEditText = (EditText) findViewById(R.id.main_edit_text_input);
        displayTextView = (TextView) findViewById(R.id.main_text_view_display);

        // Setup your shared preferences
        SharedPreferences sharedPreferences = getSharedPreferences(PreferenceConstants.TUTORIAL_PREFERENCES, MODE_PRIVATE);
        // Backup manager is what tells the android system about your preferences
        BackupManager backupManager = new BackupManager(this);
        // We wrap our shared preferences in another sharedpreferences that will also inform the backup manager
        CloudBackedSharedPreferences preferences = new CloudBackedSharedPreferences(sharedPreferences, backupManager);
        // We then wrap again so we can have convenience methods that describe what you are saving (keeping your activity cleaner)
        pref = new TutorialPreferences(preferences);
        // Set the name from shared preferences - on the very first install this will be blank, then from then on it will be whatever the user entered
        displayTextView.setText(pref.getName());
    }

    public void onSaveInput(View button) {
    	String input = inputEditText.getText().toString();
    	// Check the user has entered some text
    	if(!"".equals(input)){
    		// Update the UI
    		displayTextView.setText(input);
    		// Save to shared preferences
    		pref.saveName(input);
    	}
    }
}

Hope you find this useful, comments or questions just write below.

Source Code:

Eclipse source code download: here

GitHub repo link: here

16 thoughts on “[TUT] Backup & Restore SharedPreferences to the cloud

  1. I tried this example on emulator running on 4.03 after enabling the backup option.It worked fine but its not working on mobiles running on 2.3.4 and 4.03, I even enabled the backup and restore options on mobiles but no luck. Can you suggest please?

  2. I tested it on emulator running on 4.03 after enabling it to restore data and app worked fine, but I am unable to run the same code successfully on mobiles running on 2.3.4 and 4.03. Everything is same. Can you please suggest??

  3. I imported to eclipse and ran on both my own device and an emulator, couldn’t get it to work. I tried running “adb shell bmgr run” and everything, even put a Log.i in your BlundellBackupAgent’s onCreate method and it never gets called.

    I wonder if Google has their servers off and therefor this is silently failing without providing any warning.

    And of course I’m here after following the official walkthrough on http://developer.android.com/guide/topics/data/backup.html since that doesn’t work either.

  4. Hi

    I downloaded this and tried however only the sharedpref is backed up and restore using the bmgr commands in the shell and not through programmatically? is there anything am missing for settings….

  5. thanks to share it.
    But when I uninstall the app and install it again,the agent did not work….I Log in the BlundellBackupAgent onCreate onRestore onBackup.there were no log in logcat.
    Do u know anything ?
    thanks…..(poor English )

    1. It definitely works, download the source code and double check. Make sure you have signed up online and are using your api key. Also give it a bit of time to kick in!

      1. All the code I download from the Eclipse source
        and I directly import the project in eclipse.
        BTW, do I need change the the api key? the project package name is the same to yours. And I regist the package name com.blundell.tut, the key is the same too.Do I need to create a new project or use other packagename? I think is unnecessary. I had run the google example,the truble is same.

      2. execuse me. I wanna know the meaning of sign up.
        How to do this step?I sign it and upload to google play, not publish. I don’t know how to do next. I think the problem is here.

  6. Thank you very much for your tutorial but it seems not to working.When i do restore from cmd it works but when i reinstall the app i get “New app com.blundell.tut never backed up; scheduling” message.Do you know anything?

    1. Hi, it will work it may be the timeframe you are testing in or that your network connection is down or some other issue

      1. Well, I don’t think so.You see, when i’m testing the app at avd with android < 4.1 it works.At android 4.1 doesn't work.My phone has 4.1.1 and i cant' make it work.When it had 4.0.4 it was working.Any ideas?Thank you.

Leave a Reply

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