Mark Needham

Thoughts on Software Development

Archive for the ‘android’ tag

Learning Android: Roboguice – Injecting context into PreferenceManager

with one comment

In my last post I showed how I’d been able to write a test around saved preferences in my app by making use of a ShadowPreferenceManager but it seemed a bit hacky.

I didn’t want to have to do that for every test where I dealt with preferences – I thought it’d be better if I could wrap the preferences in an object of my own and then inject it where necessary.

Another benefit of taking this approach is that the interface of exactly what I’m storing as user preferences.

I wanted the class to be roughly like this:

public class UserPreferences {
    public String userKey() {
        return getDefaultSharedPreferences().getString("user_key", "");
    }
 
    public String userSecret() {
        return getDefaultSharedPreferences().getString("user_secret", "");
    }
 
    private SharedPreferences getDefaultSharedPreferences() {
        return PreferenceManager.getDefaultSharedPreferences(getContextHereSomehow());
    }
}

Initially it wasn’t entirely obvious how I could get a Context to pass to getDefaultSharedPreferences but I came across a blog post explaining how to do it.

What we need to do is inject a Context object via the constructor of the class and decorate the constructor with the @Inject attribute so that Roboguice will resolve the dependency:

public class UserPreferences {
    private Context context;
 
    @Inject
    public UserPreferences(Context context) {
        this.context = context;
    }
 
    public SharedPreferences getDefaultSharedPreferences() {
        return PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
    }
 
    public String userKey() {
        return getDefaultSharedPreferences().getString("user_key", "");
    }
 
    public String userSecret() {
        return getDefaultSharedPreferences().getString("user_secret", "");
    }
}

We never have to explicitly setup a binding for Context in our Roboguice because it’s already been done for us in RoboModule which is instantiated by RoboApplication which we extend like so:

public class TweetBoardApplication extends RoboApplication {
    private Module module = new RobolectricSampleModule();
 
    @Override protected void addApplicationModules(List<Module> modules) {
        modules.add(module);
    }
 
    public void setModule(Module module) {
        this.module = module;
    }
}

We then hook TweetBoardApplication up in the AndroidManifest.xml file like this:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.pivotallabs"
          android:versionCode="1"
          android:versionName="1.0">
    <application
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Light.NoTitleBar"
            android:icon="@drawable/app_icon"
            android:name="TweetBoardApplication">
   </application>
</manifest>

And that’s it!

Written by Mark Needham

January 12th, 2012 at 5:24 pm

Posted in Android

Tagged with

Learning Android: Robolectric – Testing details got saved to SharedPreferences

with one comment

I’ve been writing some tests around an app I’ve been working on using the Robolectric testing framework and one thing I wanted to do was check that an OAuth token/secret were being saved to the user’s preferences.

The code that saved the preferences looked like this:

public class AuthoriseWithTwitterActivity extends RoboActivity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(intent);
        ...
        save("fakeToken", "fakeSecret");
        ...
    }
 
    private void save(String userKey, String userSecret) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        SharedPreferences.Editor editor = settings.edit();
        editor.putString("user_key", userKey);
        editor.putString("user_secret", userSecret);
        editor.commit();
    }
}

This is an outline of what I wanted to do in the test:

@RunWith(InjectedTestRunner.class)
public class AwesomeTest {
    @Test public void shouldSaveOAuthDetails() {
        activity.onCreate(null);
 
        ShadowIntent shadowIntent = shadowOf(activity).getNextStartedActivity();
 
        // Get SharedPreferences and check 'fakeToken' and 'fakeSecret' are stored. 
    }
}

In Robolectric it’s possible to replace classes with shadow versions of themselves which get used in the test so I first created a shadow version of PreferenceManager:

@Implements(PreferenceManager.class)
public class ShadowPreferenceManager {
    private static SharedPreferences  preferences = new TestSharedPreferences(new HashMap<String, Map<String, Object>>(), "__default__", Context.MODE_PRIVATE);
 
    @Implementation
    public static SharedPreferences getDefaultSharedPreferences(Context context) {
        return preferences;
    }
 
    public static void reset() {
        preferences = new TestSharedPreferences(new HashMap<String, Map<String, Object>>(), "__default__", Context.MODE_PRIVATE);
    }
}

I had to make preferences a static variable here so that it’ll retain state. It’s a bit hacky but it’ll do for now.

Then to hook it up I had to change my test to read like this:

@RunWith(InjectedTestRunner.class)
public class AwesomeTest {
    @Test public void shouldSaveOAuthDetails() {
        Robolectric.bindShadowClass(ShadowPreferenceManager.class);
        activity.onCreate(null);
 
        ShadowIntent shadowIntent = shadowOf(activity).getNextStartedActivity();
 
        SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
        assertThat(defaultSharedPreferences.getString("user_key", ""), equalTo("fakeToken"));
        assertThat(defaultSharedPreferences.getString("user_secret", ""), equalTo("fakeSecret"));
 
        ShadowPreferenceManager.reset();
    }
}

The InjectedTestRunner class used here is pretty much like the one in the Robolectric code base.

There is actually a ShadowPreferenceManager in the Robolectric library but it doesn’t seem to store preferences anywhere as far as I can tell so it wasn’t quite what I wanted.

Written by Mark Needham

January 10th, 2012 at 9:53 am

Posted in Android

Tagged with

Learning Android: Getting android-support jar/compatability package as a Maven dependency

with 4 comments

In the app I’m working on I make use of the ViewPager class which is only available in the compatibility package from revisions 3 upwards.

Initially I followed the instructions on the developer guide to get hold of the jar but now that I’m trying to adapt my code to fit the RobolectricSample, as I mentioned in my previous post, I needed to hook it up as a Maven dependency.

I added the dependency to my pom.xml like this:

<dependency>
  <groupId>android.support</groupId>
  <artifactId>compatibility-v4</artifactId>
  <version>r6</version>
</dependency>

But when I tried to resolve the dependencies (via ‘mvn test’) I ended up with this error:

Downloading: http://repo1.maven.org/maven2/android/support/compatibility-v4/r6/compatibility-v4-r6.pom
[WARNING] The POM for android.support:compatibility-v4:jar:r6 is missing, no dependency information available
Downloading: http://repo1.maven.org/maven2/android/support/compatibility-v4/r6/compatibility-v4-r6.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.878s
[INFO] Finished at: Sun Jan 08 20:42:17 GMT 2012
[INFO] Final Memory: 8M/554M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project tweetboard: Could not resolve dependencies for project com.markhneedham:tweetboard:apk:1.0.0-SNAPSHOT: Could not find artifact android.support:compatibility-v4:jar:r6 in central (http://repo1.maven.org/maven2) -> [Help 1]

A bit of googling led me to a demo project showing how to hook up the compatibility package. It linked to the Maven Android SDK Deployer which is:

The Maven Android SDK Deployer is a helper maven project that can be used to install the libraries necessary to build Android applications with Maven and the Android Maven Plugin directly from your local Android SDK installation.

I had to first clone that git repository:

git clone git://github.com/mosabua/maven-android-sdk-deployer.git

And then find the compatability-v4 package and install it:

$ cd maven-android-sdk-deployer
$ cd extras/compatibility-v4
$ mvn clean install

I initially made the mistake of not setting $ANDROID_HOME to the location of the Android SDK on my machine, which led to the following error:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.484s
[INFO] Finished at: Sun Jan 08 20:51:07 GMT 2012
[INFO] Final Memory: 3M/81M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.codehaus.mojo:properties-maven-plugin:1.0-alpha-2:read-project-properties (default) on project android-extras: Properties file not found: /Users/mneedham/github/maven-android-sdk-deployer/extras/${env.ANDROID_HOME}/extras/android/support/source.properties -> [Help 1]

Setting it solves the problem:

$ export ANDROID_HOME=/Users/mneedham/github/android/android-sdk-macosx

There are more detailed instructions on the home page of the github project.

Written by Mark Needham

January 8th, 2012 at 8:56 pm

Posted in Android

Tagged with

Learning Android: java.lang.OutOfMemoryError: Java heap space with android-maven-plugin

with 3 comments

I’ve been trying to adapt my Android application to fit into the structure of the RobolectricSample so that I can add some tests around my code but I was running into a problem when trying to deploy the application.

To deploy the application you need to run the following command:

mvn package android:deploy

Which was resulting in the following error:

[INFO] UNEXPECTED TOP-LEVEL ERROR:
[INFO] java.lang.OutOfMemoryError: Java heap space
[INFO] 	at com.android.dx.rop.code.PlainInsn.withNewRegisters(PlainInsn.java:152)
[INFO] 	at com.android.dx.ssa.NormalSsaInsn.toRopInsn(NormalSsaInsn.java:121)
[INFO] 	at com.android.dx.ssa.back.SsaToRop.convertInsns(SsaToRop.java:342)
[INFO] 	at com.android.dx.ssa.back.SsaToRop.convertBasicBlock(SsaToRop.java:323)
[INFO] 	at com.android.dx.ssa.back.SsaToRop.convertBasicBlocks(SsaToRop.java:260)
[INFO] 	at com.android.dx.ssa.back.SsaToRop.convert(SsaToRop.java:124)
[INFO] 	at com.android.dx.ssa.back.SsaToRop.convertToRopMethod(SsaToRop.java:70)
[INFO] 	at com.android.dx.ssa.Optimizer.optimize(Optimizer.java:102)
[INFO] 	at com.android.dx.ssa.Optimizer.optimize(Optimizer.java:73)

I’d added a few dependencies to the original pom.xml file so I figured on of those must be causing the problem and eventually narrowed it down to be the twitter4j-core library which I had defined like this in the pom.xml file:

<dependency>
  <groupId>org.twitter4j</groupId>
  <artifactId>twitter4j-core</artifactId>
  <version>[2.2,)</version>
</dependency>

I found a bug report for the maven-android-plugin which suggested that increasing the heap size might solve the problem.

That section of the pom.xml file ended up looking like this:

<plugin>
  <groupId>com.jayway.maven.plugins.android.generation2</groupId>
  <artifactId>android-maven-plugin</artifactId>
  <version>3.0.0-alpha-13</version>
  <configuration>
    <sdk>
      <platform>10</platform>
      <path>/path/to/android-sdk</path>                
    </sdk>
    <dex>                                
      <jvmArguments>
        <jvmArgument>-Xms256m</jvmArgument>                                        
        <jvmArgument>-Xmx512m</jvmArgument>
      </jvmArguments>                        
    </dex>
    <undeployBeforeDeploy>true</undeployBeforeDeploy>
  </configuration>
  <extensions>true</extensions>
</plugin>

That seemed to get rid of the problem but I also tried changing the plugin version to the latest one and that seemed to solve the problem as well without the need to add the JVM arguments:

<plugin>
  <groupId>com.jayway.maven.plugins.android.generation2</groupId>
  <artifactId>android-maven-plugin</artifactId>
  <configuration>
    <sdk>
      <platform>10</platform>
      <path>/path/to/android-sdk</path>                
    </sdk>
    <undeployBeforeDeploy>true</undeployBeforeDeploy>
  </configuration>
  <extensions>true</extensions>
</plugin>

The latest version is 3.0.2 from what I can tell.

Written by Mark Needham

January 7th, 2012 at 5:14 pm

Posted in Android

Tagged with

Learning Android: Freezing the UI with a BroadcastReceiver

without comments

As I mentioned in a previous post I recently wrote some code in my Android app to inform a BroadcastReceiver whenever a service processed a tweet with a link in it but in implementing this I managed to freeze the UI every time that happened.

I made the stupid (in hindsight) mistake of not realising that I shouldn’t be doing a lot of logic in BroadcastReceiver.onReceive since that bit of code gets executed on the UI thread.

The service code which raises the broadcast message is the same as in the previous post:

public class TweetService extends IntentService {
    ...
    @Override
    protected void onHandleIntent(Intent intent) {
        StatusListener listener = new UserStreamListener() {
           // override a whole load of methods - removed for brevity
 
            public void onStatus(Status status) {
                String theTweet = status.getText();
                if (status.getText().contains("http://")) {
                    Intent tweetMessage = new Intent(TweetTask.NEW_TWEET);
                    tweetMessage.putExtra(android.content.Intent.EXTRA_TEXT, status.getText());
                    sendBroadcast(tweetMessage);
                }
 
            }
        };
 
        // code to connect to the twitter streaming API
    }
}

That is then handled like this by the BroadcastReceiver:

public class MyActivity extends Activity {
    protected void onPause() {
        super.onPause();
        if (dataUpdateReceiver != null) unregisterReceiver(dataUpdateReceiver);
    }
 
    protected void onResume() {
        super.onResume();
        if (dataUpdateReceiver == null) dataUpdateReceiver = new DataUpdateReceiver();
        IntentFilter intentFilter = new IntentFilter(TweetTask.NEW_TWEET);
        registerReceiver(dataUpdateReceiver, intentFilter);
    }
 
    private class DataUpdateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(TweetTask.NEW_TWEET)) {
                Pattern p = Pattern.compile("(http://[^\\s]+)");
                String theTweet = intent.getStringExtra(TweetTask.NEW_TWEET);
                Matcher matcher = p.matcher(theTweet);
 
                int startIndex = -1;
                int endIndex = -1;
                while (matcher.find()) {
                    startIndex = matcher.start();
                    endIndex = matcher.end();
                }
 
                if (startIndex != -1 && endIndex != -1) {
                    String resolvedUrl = resolveUrl(theTweet.substring(startIndex, endIndex));
                    saveToDatabase(resolvedUrl);
                    updateUI(resolvedUrl);
                }
            }
        }
    }
}

In particular the ‘resolveUrl’ line was probably the one one causing the problem since it makes a network call to resolve URLs from link shorteners.

To stop the screen freezing up I just needed to move most of the code from BroadcastReceiver into the TweetService:

public class TweetService extends IntentService {
    ...
    @Override
    protected void onHandleIntent(Intent intent) {
        StatusListener listener = new UserStreamListener() {
           // override a whole load of methods - removed for brevity
 
            public void onStatus(Status status) {
                String theTweet = status.getText();
                if (status.getText().contains("http://")) {
                    Pattern p = Pattern.compile("(http://[^\\s]+)");
                    Matcher matcher = p.matcher(theTweet);
 
                    int startIndex = -1;
                    int endIndex = -1;
                    while (matcher.find()) {
                        startIndex = matcher.start();
                        endIndex = matcher.end();
                    }
 
                    if (startIndex != -1 && endIndex != -1) {
                        String resolvedUrl = resolveUrl(theTweet.substring(startIndex, endIndex));
                        saveToDatabase(resolvedUrl);
 
                        Intent tweetMessage = new Intent(TweetTask.NEW_TWEET);
                        tweetMessage.putExtra(android.content.Intent.EXTRA_TEXT, resolvedUrl);
                        sendBroadcast(tweetMessage);
                    }
                }
            }
        };
 
        // code to connect to the twitter streaming API
    }
}

And then the code for BroadcastReceiver becomes much simpler which means we’re doing less work on the UI thread:

    private class DataUpdateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(TweetTask.NEW_TWEET)) {
                String url = intent.getStringExtra(TweetTask.NEW_TWEET);
                updateUI(url);
            }
        }
    }

And the freezing up of the UI is gone!

Written by Mark Needham

January 6th, 2012 at 11:40 pm

Posted in Android

Tagged with

Learning Android: Getting a service to communicate with an activity

with 2 comments

In the app I’m working on I created a service which runs in the background away from the main UI thread consuming the Twitter streaming API using twitter4j.

It looks like this:

public class TweetService extends IntentService {
    String consumerKey = "TwitterConsumerKey";
    String consumerSecret = "TwitterConsumerSecret";
 
    public TweetService() {
        super("Tweet Service");
    }
 
    @Override
    protected void onHandleIntent(Intent intent) {
        AccessToken accessToken = createAccessToken();
 
        StatusListener listener = new UserStreamListener() {
           // override a whole load of methods - removed for brevity
 
            public void onStatus(Status status) {
                String theTweet = status.getText();
                if (status.getText().contains("http://")) {
                    // do something with the tweet
                }
 
            }
        };
        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
        configurationBuilder.setOAuthConsumerKey(consumerKey);
        configurationBuilder.setOAuthConsumerSecret(consumerSecret);
 
        TwitterStream twitterStream = new TwitterStreamFactory(configurationBuilder.build()).getInstance(accessToken);
        twitterStream.addListener(listener);
        twitterStream.user();
    }
}

That gets called from MyActivity like so:

public class MyActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(this, TweetService.class);
        startService(intent);
    }
}

I wanted to be able to inform the UI each time there was a tweet which contained a link in it so that the link could be displayed on the UI.

I found a post on StackOverflow which suggested that one way to do this would be to raise a broadcast message which could then be listened to by a BroadcastReceiver in the activity.

It is possible for any other apps to listen to the broadcast message as well if they wanted to but in this case the information isn’t very important so I think it’s fine to take this approach.

I first had to change the service to look like this:

public class TweetTask {
    public static final String NEW_TWEET = "tweet_task.new_tweet";
}
 
public class TweetService extends IntentService {
    String consumerKey = "TwitterConsumerKey";
    String consumerSecret = "TwitterConsumerSecret";
 
    public TweetService() {
        super("Tweet Service");
    }
 
    @Override
    protected void onHandleIntent(Intent intent) {
        AccessToken accessToken = createAccessToken();
 
        StatusListener listener = new UserStreamListener() {
           // override a whole load of methods - removed for brevity
 
            public void onStatus(Status status) {
                String theTweet = status.getText();
                if (status.getText().contains("http://")) {
                    Intent tweetMessage = new Intent(TweetTask.NEW_TWEET);
                    tweetMessage.putExtra(android.content.Intent.EXTRA_TEXT, document);
                    sendBroadcast(tweetMessage);
                }
 
            }
        };
        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
        configurationBuilder.setOAuthConsumerKey(consumerKey);
        configurationBuilder.setOAuthConsumerSecret(consumerSecret);
 
        TwitterStream twitterStream = new TwitterStreamFactory(configurationBuilder.build()).getInstance(accessToken);
        twitterStream.addListener(listener);
        twitterStream.user();
    }
}

I then had to define the following code in MyActivity:

public class MyActivity extends Activity {
    protected void onResume() {
        super.onResume();
        if (dataUpdateReceiver == null) dataUpdateReceiver = new DataUpdateReceiver(textExtractionService);
        IntentFilter intentFilter = new IntentFilter(TweetTask.NEW_TWEET);
        registerReceiver(dataUpdateReceiver, intentFilter);
    }
 
    protected void onPause() {
        super.onPause();
        if (dataUpdateReceiver != null) unregisterReceiver(dataUpdateReceiver);
    }
 
    private class DataUpdateReceiver extends BroadcastReceiver {
        private CachedTextExtractionService textExtractionService;
 
        public DataUpdateReceiver(CachedTextExtractionService textExtractionService) {
            this.textExtractionService = textExtractionService;
        }
 
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(TweetTask.NEW_TWEET)) {
                // do something with the tweet
            }
 
        }
    }
}

Now whenever there’s a tweet with a link in it my BroadcastReceiver gets notified and I can do whatever I want with the tweet.

This seems like a reasonably simple solution to the problem so I’d be interested to know if there are any other drawbacks other than the one I identified above.

Written by Mark Needham

January 5th, 2012 at 1:41 am

Posted in Android

Tagged with

Learning Android: Authenticating with Twitter using OAuth

with 4 comments

I want to be able to get the tweets from my timeline into my app which means I need to authorise the app with Twitter using OAuth.

The last time I tried to authenticate using OAuth a couple of years ago was a bit of a failure but luckily this time Honza Pokorny has written a blog post explaining what to do.

I had to adjust the code a little bit from what’s written on his post so I thought I’d document what I’ve done.

We’re using the signpost library for which we need to download the following two jars and put them into the app’s ‘libs’ directory.

wget http://oauth-signpost.googlecode.com/files/signpost-commonshttp4-1.2.1.1.jar
wget http://oauth-signpost.googlecode.com/files/signpost-core-1.2.1.1.jar

I created a button that had to be clicked to fire the first step of OAuth authentication with twitter. The code looks like this:

public class MyActivity extends Activity {
  private String CALLBACKURL = "app://twitter";
  private String consumerKey = "TwitterConsumerKey";
  private String consumerSecret = "TwitterConsumerSecret";
 
  private OAuthProvider httpOauthprovider = new DefaultOAuthProvider("https://api.twitter.com/oauth/request_token", "https://api.twitter.com/oauth/access_token", "https://api.twitter.com/oauth/authorize");
  private CommonsHttpOAuthConsumer httpOauthConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    ImageButton oauth = (ImageButton) findViewById(R.id.oauth_button);
    oauth.setOnClickListener(new View.OnClickListener() {
      public void onClick(View v) {
        try {
          String authUrl = httpOauthprovider.retrieveRequestToken(httpOauthConsumer, CALLBACKURL);
          Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl));
          v.getContext().startActivity(intent);
        } catch (Exception e) {
          Log.w("oauth fail", e);
          Toast.makeText(v.getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
      }
    });
  }
}

The CALLBACK URL is called by Twitter when the user authorises the application (and therefore the request token). Usually it would be a HTTP URL but in this case we need to define a special URL which gets handled by our application.

The consumerKey and consumerSecret are values assigned by Twitter for the application.

The definition of the callback URL in the manifest file looks like this:

<activity android:name="MyActivity" android:label="@string/app_name" android:launchMode="singleInstance">
  ...
  <intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="app" android:host="twitter" />
  </intent-filter>
</activity>

We can change app://twitter to be anything we want but it needs to match what’s defined in the data element in our manifest file.

I found that I needed to define Callback URL in my application’s settings on Twitter otherwise I ended up getting this error:

oauth.signpost.exception.OAuthCommunicationException: Communication with the service provider failed: https://api.twitter.com/oauth/request_token
        at oauth.signpost.AbstractOAuthProvider.retrieveToken(AbstractOAuthProvider.java:214)
        at oauth.signpost.AbstractOAuthProvider.retrieveRequestToken(AbstractOAuthProvider.java:69)
...
 Caused by: java.io.FileNotFoundException: https://api.twitter.com/oauth/request_token
        at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:521)
        at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:258)
        at oauth.signpost.basic.HttpURLConnectionResponseAdapter.getContent(HttpURLConnectionResponseAdapter.java:18)
        at oauth.signpost.AbstractOAuthProvider.handleUnexpectedResponse(AbstractOAuthProvider.java:228)
        at oauth.signpost.AbstractOAuthProvider.retrieveToken(AbstractOAuthProvider.java:189)

I ended up just setting my callback URL on Twitter to the URL of this blog. It doesn’t seem to matter what you put the URL as since it’s going to be overridden by our callback URL anyway but it does need to be set.

The callback then gets handled by the following code in MyActivity

public class MyActivity extends Activity {
  ...
 
  @Override
  protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
 
    Log.w("redirect-to-app", "going to save the key and secret");
 
    Uri uri = intent.getData();
    if (uri != null && uri.toString().startsWith(CALLBACKURL)) {
 
        String verifier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);
 
        try {
            // this will populate token and token_secret in consumer
 
            httpOauthprovider.retrieveAccessToken(httpOauthConsumer, verifier);
            String userKey = httpOauthConsumer.getToken();
            String userSecret = httpOauthConsumer.getTokenSecret();
 
            // Save user_key and user_secret in user preferences and return
            SharedPreferences settings = getBaseContext().getSharedPreferences("your_app_prefs", 0);
            SharedPreferences.Editor editor = settings.edit();
            editor.putString("user_key", userKey);
            editor.putString("user_secret", userSecret);
            editor.commit();
 
        } catch (Exception e) {
 
        }
    } else {
        // Do something if the callback comes from elsewhere
    }
  }
}

…which makes another call to Twitter to get the user’s key and secret (the access token) which it then stored in shared preferences so we can use it in future without having to re-authenticate with Twitter.

We can then query Twitter like so:

HttpGet get = new HttpGet("http://api.twitter.com/1/statuses/home_timeline.json");
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setUseExpectContinue(params, false);
get.setParams(params);
 
try {
  SharedPreferences settings = getContext().getSharedPreferences("your_app_prefs", 0);
  String userKey = settings.getString("user_key", "");
  String userSecret = settings.getString("user_secret", "");
 
  httpOauthConsumer.setTokenWithSecret(userKey, userSecret);
  httpOauthConsumer.sign(get);
 
  DefaultHttpClient client = new DefaultHttpClient();
  String response = client.execute(get, new BasicResponseHandler());
  JSONArray array = new JSONArray(response);
} catch (Exception e) { 
  // handle this somehow
}

Here we retrieve the user’s key and secret which we saved on the previous step and then set them on our OAuth consumer which we use to sign our request to Twitter.

There is a nice explanation of how OAuth works about half way down this StackOverFlow post, Eran Hammer-Lahav has a pretty good “beginner’s guide to OAuth” on his blog and the OAuth spec is surprisingly readable as well.

Written by Mark Needham

January 2nd, 2012 at 2:39 am

Posted in Android

Tagged with

Learning Android: ‘Unable to start service Intent not found’

with one comment

In the Android application that I’ve been playing around with I wrote a service which consumes the Twitter streaming API which I trigger from the app’s main activity like so:

public class MyActivity extends Activity {
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        Intent intent = new Intent(this, TweetService.class);
        startService(intent);
        ...
    }
}

Where TweetService is defined roughly like this:

public class TweetService extends IntentService {
    @Override
    protected void onHandleIntent(Intent intent) {
      // Twitter streaming API stuff goes here
    }
}

Unfortunately when I tried to deploy the app the service wasn’t starting and I got this message in the log:

01-01 03:10:31.758: WARN/ActivityManager(106): Unable to start service Intent { cmp=com.example/.TweetService }: not found

What I hadn’t realised is that the service needs to be specified in the AndroidManifest.xml file but not inside the activity definition:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example" android:versionCode="1" android:versionName="1.0">
    <application android:label="@string/app_name">
        <activity android:name="MyActivity" android:label="@string/app_name" android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service android:name="TweetService"></service>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

After adding the service definition it works fine.

Written by Mark Needham

January 1st, 2012 at 3:22 am

Posted in Android

Tagged with

Learning Android: Sharing with Twitter/the ‘share via’ dialog

with 3 comments

One thing I wanted to do in the little application I’m working on was send data to other apps on my phone using the ‘share via’ dialog which I’ve seen used on the Twitter app.

In this case I wanted to send a link and its title to twitter and came across a StackOverflow post which explained how to do so.

To keep it simple I added a button to the view and then shared the data via the on click event on that button:

Button button = createButton();
button.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
    shareIntent.setType("text/plain");
    shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Stupid Scanner tricks - http://weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html");
    v.getContext().startActivity(Intent.createChooser(shareIntent, "Share via"));
  }
});
Android share via

In this case if I choose the Twitter app from the drop down list that appears it will create a new tweet with the value of ‘android.content.Intent.EXTRA_TEXT’ as its body.

If we needed to pass a subject to the other app then we could set that using ‘android.content.Intent.EXTRA_SUBJECT’ but in this case it’s unnecessary.

From what I understand so far only apps which can handle the ‘text/plain’ format will show up in the drop down list but that seems to be pretty much every app on my phone.

This works reasonably well but I wanted to see if it was possible to share directly with the Twitter app rather than having to choose from a selection of apps.

Via a combination of blog posts and StackOverflow questions I came across the following solution for posting directly to the Twitter app:

Button button = createButton();
button.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
    shareIntent.setType("text/plain");
    shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Some text");
    shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Stupid Scanner tricks - http://weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html");
 
    final PackageManager pm = v.getContext().getPackageManager();
    final List<ResolveInfo> activityList = pm.queryIntentActivities(shareIntent, 0);
    for (final ResolveInfo app : activityList) {
      if ("com.twitter.android.PostActivity".equals(app.activityInfo.name)) {
        final ActivityInfo activity = app.activityInfo;
        final ComponentName name = new ComponentName(activity.applicationInfo.packageName, activity.name);
        shareIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        shareIntent.setComponent(name);
        v.getContext().startActivity(shareIntent);
        break;
      }
    }
  }
}):;

It does depend on the Twitter app being installed on the phone to work but since the app is just for me I think it’s ok for the moment.

Written by Mark Needham

December 29th, 2011 at 10:40 pm

Posted in Android

Tagged with

Learning Android: WebView character encoding

with one comment

In my continued attempts to learn how to write an Android application I came across a problem with character encoding when trying to load some text into a WebView.

I was initially trying to write the text to the WebView like this:

WebView webview = new WebView(collection.getContext());
webview.loadData(textWithQuotesIn, "text/html", "UTF-8");
Android before after

But ended up with the output in the picture on the left hand side. I tried playing around with the encoding and debugged the application all the way through until it hit the WebView but there didn’t seem to be any problem with the text.

I eventually came across a post on StackOverflow where mice suggested using one of the other methods available for writing to a WebView.

I changed my code to read like this:

WebView webview = new WebView(collection.getContext());
webview.loadDataWithBaseURL(url, textWithQuotesIn, "text/html", "UTF-8", url);

And now the single quotes are rendering correctly as can be seen on the image on the right.

I had a quick look at the Android source code to see if there was any obvious reason why one of the methods would work and the other wouldn’t but I couldn’t see anything.

Perhaps I’m doing something wrong with my call to ‘loadData’ and that’s why it’s not rendering the character set correctly. If that’s the case please let me know.

Written by Mark Needham

December 27th, 2011 at 11:53 pm

Posted in Android

Tagged with