Android Account Manager: Introduction and Basic Implementation

Recently I had to implement an online sync facility for my habit tracker app. As it goes with every online service, you needed an account to take it’s advantage. Enter the Android Account Manager.

Now, if you are an advanced user of Android phones (and since you’re reading this, you most certainly are), you must have seen an “Accounts” section in phone Settings.

Upon clicking that, you’ll find your accounts related to apps on your phone.

I wanted to utilize this built-in AccountManager functionality to store and authenticate user’s credentials because it works like a charm with the built-in sync mechanism (using the SyncAdapter).

But we are getting ahead of ourselves here. Let’s start with the authentication system for server.

Authentication system

Our system authenticates users based on email and password combo. If the combo is correct, server returns an auth_token which are valid until the user logs out. This is your normal token based authentication where the token lives forever and has all the permissions as the user itself.

Android Account Manager's Relationship with Server
A small overview of implementation

Every device creates a new auth_token for itself because it makes it easy to log the device, limit the number of clients for an account.

This was fairly easy to implement on the server side by adding fields for auth_token in Device model. Simply create a new Device whenever user logs in or registers, populating that auth_token field to a random value.

Creating account on the phone via the Android Account Manager

Till now, we had discussed the how to create account on server, and how it interacts with the client. Let’s move on to the meat of this post, creating account using AccountManager.

Wait, what’s AccountManager? And why should we use it?

More specifically, why not store the credentials using SharedPreference and have full control without wasting time on this?

The great thing with AccountManager is that it solves the corner cases, and little small details with ease, which you might forget to account for. It really shines well when you’re using OAuth2 and need to fetch a new token when the current one expires.

There are many more benefits to using AccountManager but going through all of them is a little too much. I’ll add the links in comments if you ask for them. 🙂

A more in-depth look at AccountManager can be found here.

We’ll be using it to store the user credentials and auth_token.

Things we need to support

  1. Users can add an account
  2. They can sync their data
  3. They can log out of their account

Pretty simple and straightforward.

Flow and important terms

Before we look at the code, let’s talk about the workflow.

When the app tries to access an account, and it’s auth_token, this is how it goes :-

  • The app asks the AccountManager for an auth_token.
  • The AccountManager asks the AccountAuthenticator whether it has a token
  • If it has none, it shows the user an AccountsActivity through which the user logs in
  • The user logs in after which the app obtains the auth_token from server
  • The auth_token is stored by the AccountManager for any future use which it returns the next time app asks for it

The good thing about using AccountManager is that system will start the correct activities and handle most of the corner cases we as developers get lazy about.

This assures us that we’ll have a valid auth_token while syncing, so we can avoid defensive programming.

Building our Authenticator

Authenticator is the class which AccountManager uses to handle our account related tasks like storing the auth_token or account password.

The first thing you need to do is extend the AbstractAccountAuthenticator and implement its methods to create our Authenticator.

You may decide to implement all the methods, or just leave out some by having them throw an UnsupportedOperationException. You will, however need to implement the addAccount method to allow users to add an account in the system.

@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
                         String accountType, String authTokenType,
                         String[] requiredFeatures, Bundle options) throws NetworkErrorException {
 
    final Intent intent = new Intent(mContext, AccountsActivity.class);
 
    // This key can be anything. Try to use your domain/package
    intent.putExtra("YOUR ACCOUNT TYPE", accountType);
 
    // This key can be anything too. It's just a way of identifying the token's type (used when there are multiple permissions)
    intent.putExtra("full_access", authTokenType);
 
    // This key can be anything too. Used for your reference. Can skip it too.
    intent.putExtra("is_adding_new_account", true);
 
    // Copy this exactly from the line below.
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
 
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
 
    return bundle;
}

Another method you should implement is getAuthToken. This method allows your SyncAdapter, and really your whole app, to acquire the auth_token for making network calls.

This is how I’ve implemented it.

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle bundle) throws NetworkErrorException {
 
    AccountManager am = AccountManager.get(mContext);
 
    String authToken = am.peekAuthToken(account, authTokenType);
 
    if (TextUtils.isEmpty(authToken)) {
        authToken = HTTPNetwork.login(account.name, am.getPassword(account));
    }
 
 
    if (!TextUtils.isEmpty(authToken)) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
        result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
        return result;
    }
 
    // If you reach here, person needs to login again. or sign up
 
    // If we get here, then we couldn't access the user's password - so we
    // need to re-prompt them for their credentials. We do that by creating
    // an intent to display our AuthenticatorActivity which is the AccountsActivity in my case.
    final Intent intent = new Intent(mContext, AccountsActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
    intent.putExtra("YOUR ACCOUNT TYPE", account.type);
    intent.putExtra("full_access", authTokenType);
 
    Bundle retBundle = new Bundle();
    retBundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return retBundle;
 
}

This kind of completes the Authenticator, at least for our purposes. You can have a look at the entire file by visiting this gist for Authenticator.java.

Building the Authentication Activity

Now, you must have noticed that there is an AccountsActivity here. This is a special activity which is shown to the user whenever they have to login for Authenticator to get token.

Create the activity as you normally would, making network calls, validations and everything else normally. When you wish to add the credentials to Accounts, use a method like the one below.

public void createAccount(String email, String password, String authToken) {
    Account account = new Account(email, "YOUR ACCOUNT TYPE");
 
    AccountManager am = AccountManager.get(this);
    am.addAccountExplicitly(account, password, null);
    am.setAuthToken(account, "full_access", authToken);
}

This will create the account for you to use.

Building the Authentication Service

At this point, the Activity will be ready but we haven’t registered our Authenticator with the system.

To do that, we’ll have to define an AuthenticatorService and register it in the Manifest file with special filter and meta-data.

The service is pretty simple (link) and looks like:-

public class AuthenticatorService extends Service {
    // Instance field that stores the authenticator object
    // Notice, this is the same Authenticator class we defined earlier
    private Authenticator mAuthenticator;
    @Override
    public void onCreate() {
        // Create a new authenticator object
        mAuthenticator = new Authenticator(this);
    }
    /*
     * When the system binds to this Service to make the RPC call
     * return the authenticator's IBinder.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mAuthenticator.getIBinder();
    }
}

After making this we need to declare this service in Manifest with appropriate tags.

<service
            android:name="com.example.AuthenticatorService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>
 
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" /> <!-- We haven't defined this -->
</service>

If you notice the meta-data tag there, you’ll find android:resource‘s value something which hasn’t been defined before. This xml file is important because it lets the system know how to represent your app in the settings.

Create a folder named xml in the res folder. In that folder, make a new file named authenticator.xml (name can be anything, as long as you specify it in the Manifest file). The contents of that file for my project looks like this:-

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
android:smallIcon="@drawable/ic_launcher" />

Most of these values would have been already defined by you, except the account_type which needs to be the same as the one your provided earlier (we have used "YOUR ACCOUNT TYPE" as placeholder).

And that’s it!

This will let you create a very basic version of app supporting the accounts in your system. Look how good our app’s name looks in Account’s screen 😛

Our app seen with other apps in Android Account Manager
Look at Streaks sitting there with other apps

To access your user’s account, all you will need to do is

AccountManager accountManager = AccountManager.get(context);
Account[] accounts = accountManager.getAccountsByType(AccountConstants.ACCOUNT_TYPE);
// Use accounts[0] (or whatever number of account) after checking that accounts.length &gt; 1

That wraps up our introduction to creating an account in system. If you want a detailed information about what other things are possible, check out udinic’s post which is an excellent but overwhelming resource.

I urge you to leave a comment here if you have any questions or tweet them to me, @shobhitic.

Next week, I’ll be creating a SyncAdapter which uses this Account for communication. If you’d like to hear about that, sign up for my email list.

(The post is here – Android Sync Adapter)

 


Want these posts in your inbox?

* indicates required


29 thoughts on “Android Account Manager: Introduction and Basic Implementation

    1. In my example, I’ve used email as account name. So for me, I’ll have to get `am`, an instance of account manager and then call `am.renameAccount(account, “new email”, accountManagerCallback, handler);`

  1. >Next week, I’ll be creating a SyncAdapter which uses this Account for communication

    Didn’t make it yet?

  2. authToken = HTTPNetwork.login(account.name, am.getPassword(account));//what this line does?and if HTTPNetwork is an inbuilt class..?

    1. This basically makes a call to our own servers and gets an API Auth Token for the username password combo.

      HTTPNetwork is a custom class. You can make yours however you want, or use things like Retrofit.

  3. Hi Shobhit,

    Great post – you have explained the innards of your code – but I have an even more basic question as I am just starting to write my own app.

    What tool do I need to use to edit this code? I have Visual Studio 2017 Community and Android SDK installed.

    Does this work only on one of the OS’s or on all (at least on Android and iOS)?

    Thanks in advance.

    Tom

  4. Hi Shobhit,
    I have tried to integrate this hole thing in an app that I have developed earlier. But I don’t really understand how it works. Initially my app launched with the LoginActivity and after the login was successful the app passed to a MainActivity. But now I don’t know which is going to be my launcher Activity. Can you please explain me how it really works ? Is the system going to start the AuthenticationService before launching any activity ? Or …

    1. The AuthenticationService is launched whenever you call it, in our case MainActivity will start it to check credentials.

      MainActivity will be your Launcher activity (unless you define something else ;).

      MainActivity first checks if there is a valid login, and if there isn’t it just opens LoginActivity.

      1. As somebody already requested, the working github repo would be highly appreciated as there are quite a few black holes… for example the before mentioned AuthenticationService call is not clear to me neither. How do I call the service from the MainActivity? I see the service returns an IBinder if I am to call the onBind, but then I didn’t had any business with IBinder until now… Thanks for following along.

  5. Hi I have two apps , one is already gets published and another one is about to get published. How should i implement this with an existing app and with a new app. My requirement is that I want to login my user in either of the app, and once he is signed in one app i should get the auth token in second app. Any help will be appreciated. Thanks a Lot

  6. Hello Sobhit, how can I implement this in my application , the thing is that my first app is already published in, and the second one is about to get published soon. The requirement is that the user can login both the apps with the same user credentials. How can i implement this in existing apps that if the user logs in to one app he is automatically logged in other app as well in brief sharing the same token in both the apps. Any help will be appreciated.
    Thanks a Lot for this article.

Leave a Reply

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