Stitching together the TwitterKit in the Fabric SDK


The Android platform has missed an official Twitter SDK for years and as Android developers we’ve had to rely on third party libraries such as twitter4j to build our various twitter integrations.

Twitter4j is just a wrapper around all the commands in Twitter REST API. This means that if Twitter is not part of your core user experience it’s actually quite heavyweight and adds 2500+ methods to the method count of your project. If you then add some more third party libraries to your project you can quickly exceed the dex count limit in Android, which means that next time a product owner comes over to your desk saying, please can you add this new analytics or advertising SDK to the app you have to start working out what other stuff can be dropped or trying to get multidex builds to work on older Android phones. Not only that but because it is such a thin veneer over the API you have to implement most of the Twitter business logic around authentication tokens yourself.

This all changed last year when the new Fabric SDK was announced. For most of us developers this just meant that we needed to migrate to Fabric to ensure that we got the latest version of Crashlytics (which has fast become the crash reporting tool of choice for both Android and iOS) but otherwise, unless you’re using Twitter’s MoPub advertising framework, it wasn’t really a major event.

But the chance of reducing the method count, seamlessly integrating Twitter login, easily implementing your own Tweet UI and having an officially maintained SDK mean that the TwitterKit is also worth looking into.

Crashlytics to Fabric

Migrating from the old Crashlytics SDK to the new Fabric SDK was remarkably simple:

You need to change your old repository in both places in the build.gradle file from

maven { url 'http://download.crashlytics.com/maven' }

to

maven { url 'https://maven.fabric.io/public' }

If your gradle file is still pointing to mavenCentral() it’s probably also a good idea to migrate that to jcenter().

You then also need to change the dependency from

classpath 'com.crashlytics.tools.gradle:crashlytics-gradle:1.+'

to

classpath 'io.fabric.tools:gradle:1.+'

And then apply the fabric plugin after the android plugin (e.g.):

apply plugin: 'com.android.application'
// Must occur after the Android plugin
apply plugin: 'io.fabric'

Finally in the build script change the compile from:

compile 'com.crashlytics.android:crashlytics:1.+'

to:

compile('com.crashlytics.sdk.android:crashlytics:2.4.0@aar') {
transitive = true;
}

Where the 2.4.0 is the latest KIT_VERSION from Fabric at the time of writing (something I found very hard to discover — Fabric should update their documentation to make it easier to find the latest version of each kit).

That’s the build tools updated. Now you need to rename crashlytics.properties to fabric.properties and include both the apiKey and apiSecret for your app’s crashlytics account in there and lastly in your Application class modify:

Crashlytics.start(this);

to

Fabric.with(this, new Crashlytics());

And that’s it! All your other Crashlytics calls can stay the same and everything just works.

Something to Tweet about

Now let’s look at Twitter. Here at Depop we don’t (yet) support login with Twitter but we do allow users to connect to Twitter so that they can tweet when they’ve added a new item for sale in their shop. We do this under the settings screen like this:

Current app

So the existing app looks a bit clunky. I’ve got to type my Twitter password in, even though I’m already logged into the Twitter app and, despite saying that Depop doesn’t store the username, I can see my pre-populated username. (This is because we pull this information from the Account Manager on the phone; not from the Depop app. Still, I’m not sure that most users understand the distinction and there’s always something frightening about apps knowing your details ahead of time).

Then a switch control, which means if I connect without setting this off then I’ll automatically follow @depopmarket on Twitter.

So now I want to change this. First off, let’s update our build scripts to allow us to use the TwitterKit. One of the advantages of the Fabric SDK is that it’s modular so I only need to include the kits that I want to use and therefore don’t add lots of methods that I don’t need. Since I’ve already added the Fabric SDK I now just need to add this to the imported libraries.

compile('com.twitter.sdk.android:twitter-core:1.3.1@aar') {
transitive = true;
}

Then in the Application class again I just need to update it to include Twitter

TwitterAuthConfig authConfig = new TwitterAuthConfig(BuildConfig.TWITTER_CONSUMER_KEY, BuildConfig.TWITTER_CONSUMER_SECRET);
Fabric.with(this, new Crashlytics(), new TwitterCore(authConfig));

If you’re not using Crashlytics then obviously you don’t need that part of the statement. The with command will take a list of whatever kits I want to use. You can see that I’ve added my twitter keys to the build config too but you can use getString or even the direct values for your Twitter app here that you were previously using with twitter4j.

The Log in with Twitter section of the installation guide tells me that the easiest way to do connect to Twitter is to create a new TwitterLoginButton and add it to the Activity’s layout file. I also have to add a callback to the Button when the view is created. This callback gives me either a TwitterSession or a TwitterException error back — provided I remember to update the button with the Activity result.

So when I press this button then I actually see that it’s trying to connect to my Twitter account and what permissions I’m requesting:

New flow

That looks better. I know that Depop (the D is for the debug build that I’m running) wants to work with my Twitter account and since I’m already logged in on the Twitter app I just need to press ALLOW.

My callback on my button now returns a TwitterSession. To easily integrate this with our existing code I just convert this to and from JSON using a GsonBuilder. I can also drop all the references to the our old TwitterAccessToken class since all that information is contained in the TwitterSession.

I was a bit surprised that the callback doesn’t change the button state to logout instead of sign in but as we’ll see later, I’m not planning on keeping it anyway.

So now I’ve got everything working better than it was before but what happens if I don’t have the Twitter app on my device? I quickly uninstall Twitter, run the Depop app and I click on the button. This time instead of seeing my Twitter profile directly I get a web form showing the Depop Twitter app and I can enter the username and password again. If I’d already logged into Twitter using Chrome on my device it can also save these details. Once I login there it’s pretty much the same flow but when I click the Authorise app button this time I get a big crash!

Further investigation shows that I’m getting an empty TwitterSession object back. The reason for this is that I’d completely ignored the Callback URL in the Configure Your Twitter App part of the installation guide. Bad Barry! A quick tweak there and everything is working correctly. Now I can use single sign on or a web login.

But the product owner still isn’t happy. I’ve now got a screen showing just a login button — why doesn’t starting that activity just go straight to the authorise screen? Also I’ve now lost the ability to get the user to follow Depop on Twitter. And I’m not happy because if I display the authorise screen but press cancel I’m getting an error message displayed.

So back to the installation instructions again — they explain the simplest way but where is the more advanced way? Nothing. Oh well, looks like I’ll have to work it out for myself. Looking through the TwitterLoginButton class it turns out that I need to get a new TwitterAuthClient() and authorize it. This will display the SSO login screen or the webform appropriately. I just need to pass in a callback as before and in my onActivityResult method I do this:

if (requestCode == TwitterAuthConfig.DEFAULT_AUTH_REQUEST_CODE) {
getTwitterAuthClient().onActivityResult(requestCode, resultCode, data);

Now what about the error I’m displaying when the user cancels? Well Twitter still returns an exception in this case but it doesn’t say that it was a cancelled exception. I need to store the resultCode that I get back in the onActivityResult, then in my failure callback I can say:

@Override
public void failure(TwitterException e) {
if (twitterResultCode == RESULT_CANCELED) {
//Twitter still returns an exception even if the user cancels the action. In this case we just want to close our activity too.
finish();
} else {
... do something with the error
}

And finish my activity.

Now finally, what about that follow option? A quick look through the SDK documents shows that create friendship is not supported. I can, however, define my own API client around any of the REST API commands using retrofit. That looks like this:

public class DepopTwitterApiClient extends TwitterApiClient {
    public DepopTwitterApiClient(final TwitterSession session) {
super(session);
}
    /**
* Provide CustomService with defined endpoints
*/
public CustomService getCustomService() {
return getService(CustomService.class);
}
    interface CustomService {
@FormUrlEncoded
@POST("/1.1/friendships/create.json")
void create(@Field("screen_name") final String screenName, @Field("follow") final boolean follow, Callback<User> cb);
}
}

I can now create a dialog that appears when the user first logs in to ask them if they want to also follow @depopmarket on Twitter. If they say yes I simply invoke my new command as follows:

new DepopTwitterApiClient(data.result).getCustomService().create("@depopmarket", true, new Callback<User>() { ...

One gotcha here is that the call will normally return ok almost straightaway but the request is carried out asynchronously, so if you look at the Twitter account for that user, they may not immediately be following @depopmarket.

And that’s it! Twitter connect and Crashlytics upgraded to Fabric with a big saving on the method count and an improved UX.

We’re hiring!

Do you want to join the Depop family? We’re looking for Android engineers who can help change the way people buy and sell from their mobile using the latest android UI, coding and testing practices.

Find our vacancies here and get in touch with a copy of your CV.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.