Oh Android – you never make life easy do you? Recently I needed to add the ability to backup and restore Android shared preferences. And as any good Xamarin developer would do, I was using the Settings plugin from the prolific James Montemagno.
That library abstracts each individual platform’s complexities of saving preferences into an easy to use set of APIs – and it’s awesome. On the Android side, it uses the system’s default shared preferences for the given context. Sounds perfect… and it is, until you try to backup some data.
Android and User Preferences
Let’s back up a bit (get it, “back up a bit”?) and explore how Android manages preferences. It uses XML files to store key/value pairs of preferences. You’ll notice I used the word “files” instead of “file” … Android also gives you the ability to specify multiple files to hold preference settings. So you can slice and dice the user’s preferences by whatever means makes sense for your app.
Because multiple files can be used, you need to specify names when creating those files. Makes sense. You do not, and cannot, specify a name of the default shared preference file – the file that’s used by the Settings plugin. (However … in version 2.6 of the Settings Plugin you will be able to specify the preference file name if you choose … FYI).
No big deal, right? Well, let’s take a look at how the backup system works on Android.
The Android BackupAgentHelper
There are two parts to doing backups in Android: 1) creating the code that will run in response to backup and restore operations and 2) setting up the administrative overhead of registering everything. Let’s take a look at the code portion first.
We can go one of two ways to backup files on Android – you can either roll everything yourself, handling all the backup and restore events manually and verifying everything gets to where it needs to go… the Hard Way. Or we can extend the BackupAgentHelper class that takes care of a lot of the plumbing for us … which is the Easy Way. We’re developers, by nature we’re lazy, let’s take a look at the Easy Way (and really for shared preferences there’s no compelling reason to do it any other way).
To do it the easy way, one extends the BackupAgentHelper
class. When doing that we need to override one function, the OnCreate()
function. In there we create subclasses of the BackupHelper
class. We then add those subclasses to the BackupAgentHelper
class we just extended.
Those are a lot of words – let’s take a look at an example, it’ll make things more clear.
public class PrefsBackupAgent : BackupAgentHelper
{
public override void OnCreate()
{
// Create a new subclass of BackupHelper meant for shared preferences - this one looking for one called test
var helper = new SharedPreferencesBackupHelper(this, "test");
// Add that class so BackupAgentHelper knows to back it up (and restore it)!
AddHelper("prefs", helper);
base.OnCreate();
}
}
What this class is doing is that it is first creating a new SharedPreferencesBackupHelper
class (a subclass of BackupHelper
) – and we’re telling it to look for a preferences file named “test” (that we created elsewhere in our code). It then registers that new backup helper with the overall backup agent, so the system’s backup manager knows to backup and restore that file.
The key to the above is the fact that SharedPreferencesBackupHelper
takes the name of the shared preferences file it needs to backup … and the system’s shared default preferences (the one the Settings Plugin uses) doesn’t have a name!
The fix for it is actually kind easy … the file name for the default settings file is the application’s package name plus “_preferences”. So if we would take the code from above and modify it to work with the system’s default shared preferences, we get:
public class PrefsBackupAgent : BackupAgentHelper
{
public override void OnCreate()
{
// Create a new subclass of BackupHelper meant for shared preferences
var helper = new SharedPreferencesBackupHelper(this, ApplicationContext.PackageName + "_preferences");
// Add that class so BackupAgentHelper knows to back it up (and restore it)!
AddHelper("prefs", helper);
base.OnCreate();
}
}
Wow – that was pretty easy… actually super duper easy! Take a look at the other functions the BackupAgentHelper
class provides as well, you can hook into other callbacks (such as RestoreFinished
) as needed to do whatever customizations your app may need.
I should also note that we’re no limited to only backing up shared preferences this way … we can also do any file we created from our app, using the FileBackupHelper
class. It takes more or less the same parameters as the SharedPreferencesBackupHelper
, the context and an array of files (full paths) to backup.
Now let’s take a look at how we can test it (and add all the administrative overhead of hooking our BackupAgentHelper subclass up to the system knows to use it to perform backups and restores).
First The Admin Stuff
There are a couple of steps that we need to do in order to make sure we register our BackupAgentHelper
subclass with the OS, so it gets invoked during backup operations, and also to register our app with the Android Backup Service.
First, the easy one, registering our app so the data can go to our users’ accounts in the “Google Cloud” or the Android Backup Service. To do so, go here, enter your app’s package name, agree to the terms and conditions, and out pops an ID that we’ll need in a bit.
Then, go into your app’s AppManifest.xml and add the following node within the <application>
node.
<meta-data android:name="com.google.android.backup.api_key" android:value="THE KEY FROM THE LAST STEP GOES HERE" />
Finally, and this is the part that’s trips people up sometimes, open up the AssemblyInfo.cs and add the following line:
[assembly: Application(BackupAgent=typeof(PrefsBackupAgent), RestoreAnyVersion=true)]
What we’re doing there is programically adding more info the AppManifest.xml’s <application>
node – namely the BackupAgent
attribute. However – since Xamarin puts all generated classes into a top level package with an MD5 hash name … there’s no way we can know that at design time … so it’s easier if we use the typeof() operator at runtime to specify this.
The then BackupAgent
attribute is telling the OS that the PrefsBackupAgent
class (that we created) should be invoked during backup and restore operations.
Speaking of backup and restore operations … let’s check out how to invoke those!
The Android Debug Bridge
In order to test out the backup and restores – we’re going to need a way to manually force those operations to occur, rather than to wait on the normal operations generated by the OS. And to invoke those manually we use the Android Debug Bridge – or ADB.
The easiest way to start the ADB up, if you’re using Xamarin Studio on the Mac, is to go to Tools->SDK Command Prompt. If you’re using Visual Studio, Tools -> Android -> Android Adb Command Prompt.
The adb tool that we’ll be using is bmgr or backup manager.
Here are the steps we need to follow in order to force a backup operation.
- Start debugging your app
- Start the adb session and issue
adb start-server
command adb shell bmgr enable true
adb shell bmgr backup ‘your.package.name’
adb shell bmgr run
That will first three steps you only need to do once, and they get you app, the adb, and the backup manager up and running.
The fourth step will queue any changes that you made for backup. The fifth runs the backup operation. If you were to have a breakpoint set in the BackupAgentHelper
subclass, it would get hit at this point.
Make some changes to your app that will get persisted to the shared preferences, but don’t run a backup. Let’s restore them to where they were before. First stop debugging your app. Then issue adb shell bmgr restore ‘your.package.name’
. Run your app again, and you should see that the preferences are back in a state when the last backup was taken.
Back Up To The Top
We ran through a lot here, especially since I only wanted to talk about how to specify the Android’s default shared preferences file name for backup purposes. But, along the way we did see how Android can use many different files to store user preferences in and how we can specify each of those files to get backed up or not. We also saw how easy it was to extend the basic BackupAgentHelper
class … and with hardly any work on our part have it take part in backup and restore operations. There was a little bit of a work around in order to get Android to recognize that class as the one that should be invoked during backup operations due to Xamarin putting our classes into a top level MD5 hashed package name. But invoking the backup and restore operations was no trouble at all using the Android Debug Bridge.
Comments