Normally on Styling Android I try and stick to UI / UX tips and techniques, but in this series of articles I’m going to cover a topic that is not directly UI / UX specific yet, if implemented, can delight your users: using Android Data Backup to backup and restore app settings / config to the cloud. The technique that we’re going to explore is something that I implemented a while ago in an app that I created just for my own use, but whenever I switch devices, I’m always pleasantly surprised when my config magically reappears on the new device, and I don’t need to configure my app from scratch. Data backup is something that Google Developer Advocate Richard Hyndman has recently blogged about demonstrating how app settings can be manually backed up and restored on ICS. I can only agree with Richard’s sentiment that it is a pity that more games do not implement cloud backup to store your progress. In this series of articles we’ll look at how easy it is to implement cloud backup in your app using the built in Data Backup tools.
Before we go any further, it is worth a quick explanation of how Android Data Backup actually works. Although the framework supports different backup providers, I’m going to stick with the default backup service provided by Google because it just works straight out of the box. In order to use this service the device must be registered to the user’s Google account, and all data will be stored against that account. If the user switches devices and re-installs your app all data backed up by your app will be restored when the app is installed provided that the user has registered the new device to the same Google account. It really is very simple and I guarantee that the first time you see it working you will surprise yourself!
Data Backup is supported from Android 2.2 (API Level 8 / Froyo) onwards. If you want to target earlier versions of Android with your app, I would still suggest using this technique, but use reflection to determine whether Data Backup is supported, and simply disable it at runtime on Android versions which don’t support it.
It is worth a quick discussion of how Data Backup should be used. According to the Google documentation, it is not designed to synchronise data across multiple clients, but more a mechanism to migrate your data to new devices. In other words you should use it sparingly: Don’t store large amounts of data in the cloud, and don’t flood the cloud service with either backup or restore requests. There are mechanisms built in to prevent lots of backup requests, but it is possible to force restores, so care needs to be taken here.
OK, enough of the background information, let’s have create a simple app which contains an EditText which persists its value to SharedPreferences so that the value persists across different invocations of the app. Let’s create a 4.0.3 app named BackupRestore with a package name of com.stylingandroid.backuprestore
, and a default Activity named BackupResoreActivity
:
OnSharedPreferenceChangeListener
{
public static final String PREFS = “prefs”;
public static final String KEY = “key”;
private EditText edit;
private BackupManager backupMgr = null;
private SharedPreferences sharedPrefs;
@Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
sharedPrefs = getSharedPreferences(
BackupRestoreActivity.PREFS,
MODE_PRIVATE );
edit = (EditText) findViewById( R.id.editText );
edit.addTextChangedListener( new TextWatcher()
{
private int start = 0;
private int end = 0;
@Override
public void onTextChanged(
CharSequence s, int start,
int before, int count )
{
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(
BackupRestoreActivity.KEY,
s.toString() );
editor.commit();
}
@Override
public void beforeTextChanged(
CharSequence s, int start,
int count, int after )
{
this.start = after;
this.end = after;
}
@Override
public void afterTextChanged( Editable s )
{
edit.setSelection( start, end );
}
} );
backupMgr = new BackupManager( getApplicationContext() );
}
@Override
protected void onResume()
{
sharedPrefs.registerOnSharedPreferenceChangeListener( this );
String val = sharedPrefs.getString( KEY, null );
if ( val != null )
{
edit.setText( val );
}
super.onResume();
}
@Override
protected void onPause()
{
sharedPrefs.unregisterOnSharedPreferenceChangeListener( this );
super.onPause();
}
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences,
String key )
{
if ( key.equals( KEY ) )
{
String newVal = sharedPreferences.getString( key, null );
if ( newVal != null )
{
edit.setText( newVal );
}
}
}
}
[/java]
Hopefully this should be fairly straightforward. In onCreate
we get our EditText control and add a TextChangeListener to it so that we detect changes to the text and we store changes to the SharedPreferences as they occur. We also get a new BackupManager instance which we’ll discuss further as we progress.
In onResume
and onPause
we register and unregister an OnSharePreferneceChangeListener so that we can detect changes made to SharedPreferences when a restore occurs. We handle this in onSharedPreferenceChanged and simply set the value of our EditText control to whatever is the new SharedPreferences value is.
It is worth noting that we have defined a couple of constants which we’ll use later.
There’s also the layout file in res/layout/main.xml:
[xml]
[/xml]
So we have a layout containing our EditText control and two buttons labelled “Backup” and “Restore”.
If we run this, we can change the value in the EditText control, and it survives the app shutting down or even being forcefully stopped. If we uninstall and re-install the app, the data is lost because the app’s data is removed when the app is uninstalled.
Next let’s add Data Backup support. The first things that we need to do is register for an API key, and add this to our Manifest:
[xml]
[/xml]
I have removed my API key for obvious reasons, and you’ll need to generate your own and put it in the appropriate place. We have also added android:backupAgent="PrefsBackupAgent"
to the
element. This defines the BackupAgent implementation that will be responsible for backing up and restoring our data. Let’s look at the implementation of this:
{
private static final String PREFS_BACKUP_KEY = “prefs”;
@Override
public void onCreate()
{
SharedPreferencesBackupHelper helper =
new SharedPreferencesBackupHelper(
this,
BackupRestoreActivity.PREFS );
addHelper( PREFS_BACKUP_KEY, helper );
}
}
[/java]
How simple is that? All we are doing is creating a new instance of SharedPreferencesBackupHelper and using one of the constants that we defined in BackupRestoreActivity to ensure that we are connecting to the correct SharedPreferences.
The only thing that remains is to wire up the two buttons on our layout to perform the necessary backup and restore operations. To do this we need to create two additional methods in BackupRestoreActivity which correspond to the android:onClick
attributes on our buttons in the layout:
{
backupMgr.dataChanged();
}
public void restore( View v )
{
backupMgr.requestRestore( new RestoreObserver()
{
@Override
public void restoreStarting( int numPackages )
{
super.restoreStarting( numPackages );
}
@Override
public void restoreFinished( int error )
{
super.restoreFinished( error );
}
@Override
public void onUpdate( int nowBeingRestored, String currentPackage )
{
super.onUpdate( nowBeingRestored, currentPackage );
}
} );
}
[/java]
The backup
method is simplicity itself, we simply call dataChanged() on the BackupManager instance that we obtained in onCreate(). The restore
method is slightly more complex because we mus create a RestoreObserver, but in essence we are simply calling requestRestore() on the BackupManager instance.
That is our backup and restore fully implemented! We can test it by entering a value in the EditText box and clicking “Backup”. This will queue the backup request which will actually take place some time later (this is part of the flood protection that I mentioned earlier). However, for development purposes we can force the backup to run immediately using adb:
adb shell bmgr run
If we now uninstall and re-install the app, we should see that the EditText value is magically restored! However, if we manually change the value of the EditText and click “Restore” we would expect the value of the EditText control to revert back because our OnSharedPreferenceChangeListener should respond to the restore operation changing the SharedPreference, yet it doesn’t. In the concluding article of this series we’ll learn why this is, and explore the backup / restore mechanism in a little more depth to overcome this problem.
The source code for this article can be found here.
© 2012, Mark Allison. All rights reserved.
Copyright © 2012 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
This might not be UI related, but UX it nevertheless is.
As a user I profit by my data being transfered. And it makes switching devices much easier for all that stuff that is not already up in the cloud. So any app having a feature like this is more usable after a device change than any not having it.
Now it’s up to us developers to listen to you and follow your blog posts 🙂
Backup Agent is insecure. Your backup key is… Everyone can obtain this key by using your package name. So it can use it to retrieve its own data with another application. That is not critical because you can obtain only data attached to your account; but that’s a fail.
Otherwise, in your example, your data is saved locally and not saved in the cloud. You need to wait one hour for a network backup. And if you change phone, nothing comes…
Seriously it is a joke for game developper.
You’re right that the key is insecure, but only the app with the correct package name declared in the Manifest can use that key. So unless you have my signing certificate (which I’m pretty sure you don’t) you cannot put an app in the Market which uses my backup key.
I do actually mention in the article that the backup to the cloud does not occur immediately.
Also, data *does* come back if you change phones. I change devices regularly and, provided I activate each device using the same Google ID, the data comes back every time.
Hello,
Thanks for your reply, i use your code with my own package name (and my key) without success. I have publish my app called com.iopixeltest.backupsuccess. The backup seems ok (adb bugreport) but when i reset my phone, nothing is restored. My app isn’t reinstalled by google too. I didn’t understand the problem [I have waited a night]
Thanks for your help,