Tuesday, February 26, 2013

Resistance/Avalon App

I've once again taken a detour from my more serious Android development to do some silly side projects.

The first of them is an app for the board game The Resistance and The Resistance: Avalon.  I've recently become obsessed with this game because it's just a ton of fun.  The game involves a lot of hidden roles, so there's a lengthy boot-up sequence where each person's allegiances are determined.  When you use all the roles available, it gets to be a bit of chore.  So I've written a dumb app that uses Android's TTS to speak the setup out loud, making the process a tiny bit easier.

The application can be found here: https://play.google.com/store/apps/details?id=com.idunnolol.resistance

Open source code here: https://github.com/dlew/android-resistance

As is usually the case with side projects, the time I put into the project vastly outweighs the time I'll ever save by using the app.  As such, I took this as an opportunity to try out two things: Android's TextToSpeech capabilities, and Maven Android builds.

TextToSpeech

I found TextToSpeech to be far easier to use than I expected.  It took me almost no time to get it up and running.  The only snag I ran into was using the OnUtteranceCompletedListener.  You need to give an utterance id to something you play before the listener will fire:

HashMap<String, String> params = new HashMap<String, String>();
endParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "myUtteranceId");
mTTS.speak("Say Something", TextToSpeech.QUEUE_ADD, params);

Maven Android Builds

I've been woefully behind the times with regards to Android build technology.  For years I've seen open source github projects using Maven but I've always ignored it because I'm scared of the big angry pom.xml files.  So I determined that I would use this simple app to teach myself Maven (via maven-android-plugin).

I found the initial setup of Maven to be pretty simple.  I had the samples up and running in no time using the "getting started" section of the site.  I even got Eclipse building the application using Maven using m2e-android.  So far so good.

I ran into a brick wall when I tried to add a library (in particular, ActionBarSherlock).  The command line Maven worked just fine when I added the library dependency, but I happen to enjoy the amenities of a modern IDE so it must work in Eclipse.  But in Eclipse, it wouldn't build - it complained about a missing dependency.  It turns out that you need to still manually do stuff for each library anyways if you're using Eclipse + Maven (unless I'm mis-reading the state of apklib, which is entirely possible).  Wasn't that the whole reason I started to use Maven in the first place?  To simplify my build process?

I think I'll keep making pom.xml for command line building/testing, but for actual dev in Eclipse it actually sets me back to use Maven.  Perhaps it integrates better with IntelliJ?  That alone may be reason to switch.  But at this point I'm far more excited for the upcoming Gradle builds.

One More Thing

If there was one cool thing I did with the code, it was the setup of Config.java.  Originally I had it with a bunch of booleans, one for each option; but this led to a lot of switch-like code that just felt repetitive. By converting it to an enum keyed-boolean store, I was able to automate a lot of app.  I always love it when you can greatly simplify and condense the code at the same time.

Tuesday, November 20, 2012

Why Do Motorola's ListViews Look Different

There was a helpful article "Why Does My ListView Look Different?" that Motorola posted a few years ago.  Unfortunately it has disappeared from the internet, as you might tell by clicking on the link.  It had some crucial information on fixing a weirdness for Motorola devices that I wanted to preserve for posterity (in case anyone else starts looking for it).

Essentially, when you're on some Motorola devices, the bottom of the ListView has a shadow on it by default.  In addition to that, if the ListView is supposed to take up the entire screen (even if it's not full), it doesn't.  Here's a screenshot of the bug in action:


The problem is android:overScrollFooter.  Motorola has a default one set and it causes sadness.  To get rid of it, set android:overScrollFooter="@null" in your ListView.  For bonus points, set it in a style and set that as your theme's default ListView so you never have to deal with this problem again.

Monday, November 12, 2012

The Unknown Style: actionBarWidgetTheme

One of the cooler features I've found in the Android Action Bar is grouped menu items.  If you group together a series of menu items in a submenu, you get a pretty popup of those items.  For example, if you set a submenu with android:checkableBehavior="single" then it'll have a radio button next to each item in the popup menu:



I wanted to reskin the RadioButton so that they all looked Holo, even on versions using ActionBarSherlock.  Naturally, I turned to my theme and set the radioButtonStyle.  However, the code below did not work:
<style name="MyTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
    <item name="android:radioButtonStyle">@style/MyRadioButtonStyle</item>
</style>

<style name="MyRadioButtonStyle" parent="@android:style/Widget.CompoundButton.RadioButton">
    <item name="android:button">@drawable/my_radio_button</item>
</style>

I was fairly lost for a few hours because radioButtonStyle was clearly working on everything except for the widget popup.  I could insert RadioButtons into my Activity and they'd pick up the new style, but the old style would remain for the action bar.  What was going on?

The culprit is android:actionBarWidgetTheme.

Introduced in API 14, what it does is let you style Views inflated by the action bar separately from the rest of your Application or Activity.  This is a neat trick, but if you don't know it exists, you could easily get lost on why your styles aren't applying to action bar Views.

If android:actionBarWidgetTheme is undefined, it falls back to the current theme's values.  For most Android themes, that means you're fine.  But in the case of Theme.Holo.Light.DarkActionBar, it does set the actionBarWidgetTheme to "@android:style/Theme.Holo".  As a result, it will not fallback to your theme's default value.

I solved the problem by setting my own actionBarWidgetTheme that is a sub-style of the original actionBarWidgetTheme:

<style name="MyTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
    <item name="android:actionBarWidgetTheme">@style/MyActionBarWidgetTheme</item>
</style>

<style name="MyActionBarWidgetTheme" parent="@android:style/Theme.Holo">
    <item name="android:radioButtonStyle">@style/MyRadioButtonStyle</item>
</style>

It's important to properly parent the actionBarWidgetTheme with whatever the theme was previously setting as its parent; otherwise you may miss out on some styling elsewhere.

Friday, November 9, 2012

Styling an AutoCompleteTextView

I ran into some confusion recently styling some AutoCompleteTextViews.  An AutoCompleteTextView is a compound View - it's got both an EditText component and a floating dropdown component.  The former is rather straightforward to style, by the dropdown is difficult because it's a mixture of attributes on the AutoCompleteTextView itself and styles set in the theme via android:dropDownListViewStyle.

For example, if you want to setup a custom background on the dropdown, you do it in the AutoCompleteTextView:

<AutoCompleteTextView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:popupBackground="@drawable/bg_autocomplete" />

But if you want to change the dividers, you have to create a theme and point that to a style, which isn't an immediately obvious solution:

<style name="MyTheme">
  <item name="android:dropDownListViewStyle">@style/DropDownListViewStyle</item>
</style>

<style name="DropDownListViewStyle">
  <item name="android:divider">#4F4F4F</item>
  <item name="android:dividerHeight">1dp</item>
</style>

The worst is when attributes in the AutoCompleteTextView and normal ListView styles collide.  I made the mistake of assuming that since it adopts a ListView-like style, it would adopt all ListView attributes - like android:listSelector.  The below, however, does not work:

<style name="DropDownListViewStyle">
  <item name="android:listSelector">@drawable/list_selector</item>
</style>

Instead, AutoCompleteTextView has its own attribute, android:dropDownSelector.  You have to set it like this:

<AutoCompleteTextView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:dropDownSelector="@drawable/list_selector" />

Monday, September 10, 2012

The Case of the Twin onSurfaceChanged() and the Split Action Bar

There's a funny little timing problem I ran into recently I'd like to share regarding window size and split action bars.

In an app I've got a loading screen that displays as we're making network requests.  It's a cute SurfaceView with some animation to keep users amused.  I'd written most of the initialization of assets inside of onSurfaceChanged() because I wanted to pre-scale everything to the exact size of the surface.

But a problem arose - for some reason onSurfaceChanged() was being called twice (and only when in portrait, not in landscape).  This was troublesome because it's somewhat a substantial method due to all the initialization.  What was happening?

It turns out that the split action bar was the culprit.  Here's how things went down:

1. In onCreate(), add the SurfaceView to the view hierarchy.

2. SurfaceView.onSurfaceChanged() would be called.

3. onCreateOptionsMenu() would then be called.  Since I had splitActionBarWhenNarrow set for the Activity, this would create the split action bar.

4. Due to the split action bar, the window size would change.  SurfaceView.onSurfaceChanged() would be called a second time.

The solution was to move the addition of the Fragment with the SurfaceView somewhere after onCreateOptionsMenu().  I put it in onPostResume() (but make sure to call it only once on initialization).

There is one alternative solution, which is to call invalidateOptionsMenu() (or supportInvalidateOptionsMenu(), if you're using the support library) during onCreate().  This will force onCreateOptionsMenu() to be called early.  I dislike this answer because it screws up the normal lifecycle for menu creation and can therefore readily cause confusing issues down the line.

Wednesday, August 29, 2012

Manually Adding the Legacy Overflow Button when targetSdkVersion >= 14

My company is in the process of converting our apps to use action bars, but we're not quite there yet.  In the meantime, we've depended on the legacy overflow button on devices without a dedicated menu button:



A problem has arisen because we've bumped our targetSdkVersion up to 16 (Jelly Bean).  It turns out there is a specific set of rules for when the legacy overflow button appears.  Our app was depending on the fact that our minSdkVersion was less than 10 and that our targetSdkVersion was between 11 and 13.  By bumping our targetSdkVersion up to 16, we no longer got the legacy overflow button.

This situation wouldn't be a problem if we showed an action bar, since the overflow menu would just appear there.  However, we're using a theme that disables the title, so our menu items disappeared completely.

I want to emphasize that the correct solution is to use an action bar with an overflow button.  But we're not done with the conversion to action bars yet and during development we'll want that menu to be accessible (all of our debug options go in there).  So I came up with a small hack that lets us enable the legacy overflow button manually.

It turns out there is a hidden Window flag - WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY - that determines whether the legacy overflow button is shown or not.  By using reflection, we can retrieve this field and add it to the Window's flags:

public static void addLegacyOverflowButton(Window window) {
  if (window.peekDecorView() == null) {
    throw new RuntimeException("Must call addLegacyOverflowButton() after setContentView()");
  }

  try {
    window.addFlags(WindowManager.LayoutParams.class.getField("FLAG_NEEDS_MENU_KEY").getInt(null));
  }
  catch (NoSuchFieldException e) {
    // Ignore since this field won't exist in most versions of Android
  }
  catch (IllegalAccessException e) {
    Log.w(TAG, "Could not access FLAG_NEEDS_MENU_KEY in addLegacyOverflowButton()", e);
  }
}

I added the RuntimeException above because you need to call this after the decor View is set (or else it does nothing).

Friday, July 20, 2012

Android Emulator Keyboard Support in ADT 20+

If you've upgraded to ADT 20, you may have noticed that your keyboard no longer works on inputs on your emulator.  That is, when you have an EditText on the screen, typing characters into your physical keyboard doesn't register anything in the emulator; you need to open the soft keyboard to do anything.

The problem is that the emulator no longer assumes you have keyboard support by default.  You'll need to add it yourself to the emulator and your keyboard will work again: