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.
I post random things I've learned while coding with the hope that it will save people time and effort.
Tuesday, November 20, 2012
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:
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:
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.
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:
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:
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:
Instead, AutoCompleteTextView has its own attribute, android:dropDownSelector. You have to set it like this:
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.
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:
I added the RuntimeException above because you need to call this after the decor View is set (or else it does nothing).
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:
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:
Tuesday, June 12, 2012
Fragment Transactions Reference
I recently found myself looking at Fragment transactions and wondering precisely what each Fragment state meant. I vaguely had an idea of what added, attached, shown, hidden, detached and removed meant, but not the specific behaviors of each. I spent a morning looking through Android's code to better understand what happens to Fragments.
Here's a reference manual for what I learned. I looked into two aspects of Fragments: fragment states, and animations in transactions. This research was done primarily on the compatibility library. It's possible that some of the details below will differ for the 3.x+ implementation of Fragments.
Fragment States
All of the below support animations. Terminology note: when I say "configuration change", I mean when the device changes configuration (for example, from portrait to landscape) and the Activity must be destroyed/recreated.
FragmentTransaction.add()/remove() - These add and remove a Fragment from the FragmentManager. By default, it will begin showing the Fragment as well. During the transaction the returns of Fragment.isAdded() and Fragment.isRemoved() will be modified. These states are preserved on configuration change, so a Fragment that's added will remain added and can be retrieved again from the FragmentManager. Since a removed Fragment is no longer part of the FragmentManager, it will not be retrievable after a configuration change either.
FragmentTransaction.attach()/detach() - These attach/detach a Fragment from the Activity (e.g., make it an active Fragment that can be seen). During the transaction the returns of Fragment.isAdded() and Fragment.isDetached() will be changed. These states are preserved on configuration change, which means that (unlike removing a Fragment) it is possible to detach a Fragment but then retrieve it later after configuration change (even if it's not actively being displayed). Even in a detached state, the Fragment runs through all lifecycle methods (except the ones that are related to being attached to an Activity) - for example, onSaveInstanceState() will still be called even if the Fragment is detached (which is quite useful for maintaining state).
FragmentTransaction.show()/hide() - These show or hide a Fragment (in particular, it calls View.setVisibility() on the Fragment's root View). During the transaction the returns of Fragment.isHidden() will be modified. Showing/hiding Fragments states are NOT maintained on configuration change, meaning that if you hide a Fragment it will reappear on config change (for example, if the user rotates the screen).
FragmentTransaction.replace() - Theoretically it is the exact same as removing all Fragments associated with a particular View, then adding a new Fragment in its place. I did not research this much (since I'm more interested in manual control of which Fragments are shown).
Fragment Animations
There are three different ways to animate Fragment transactions. These are ordered by precedence (in other words, the first method, if available, will be used; then the second, then the last).
One thing to note between the compatibility library and the standard Fragment library is that the standard library uses property animators, whereas the compatibility library uses tween Animations.
1. Override your Fragment's onCreateAnimator() method and return an animation. This is the only way to dynamically generate an animation in code (the other two methods rely on pre-configured animations from your resources directory). In the compatibility library, the method is onCreateAnimation() (since the compatibility library uses animations instead of animators).
2. Call FragmentTransaction.setCustomAnimations(), referencing either animators or animations (depending on whether you're using the compatibility library or not). What is interesting is that setCustomAnimations() affects all fragment transitions added to the transaction after it is called. So you need to call setCustomAnimations() before you want it used, and you can actually setup multiple different custom animations for each part of a transaction (with a call to setCustomAnimations() before each add()/remove()/attach()/detach()/show()/hide()/replace()).
3. Setup a style that defines window animations (from this style), then call FragmentTransaction.setTransition() and FragmentTransaction.setTransitionStyle() to specify that style and target the correct animation.
#3 works different in the compatibility library. FragmentTransaction.setTransitionStyle() is completely ignored. Instead, it uses the specified transition to select a stock animation. So if you want custom animations in the compatibility library, you must use #1 or #2.
Here's a reference manual for what I learned. I looked into two aspects of Fragments: fragment states, and animations in transactions. This research was done primarily on the compatibility library. It's possible that some of the details below will differ for the 3.x+ implementation of Fragments.
Fragment States
All of the below support animations. Terminology note: when I say "configuration change", I mean when the device changes configuration (for example, from portrait to landscape) and the Activity must be destroyed/recreated.
FragmentTransaction.add()/remove() - These add and remove a Fragment from the FragmentManager. By default, it will begin showing the Fragment as well. During the transaction the returns of Fragment.isAdded() and Fragment.isRemoved() will be modified. These states are preserved on configuration change, so a Fragment that's added will remain added and can be retrieved again from the FragmentManager. Since a removed Fragment is no longer part of the FragmentManager, it will not be retrievable after a configuration change either.
FragmentTransaction.attach()/detach() - These attach/detach a Fragment from the Activity (e.g., make it an active Fragment that can be seen). During the transaction the returns of Fragment.isAdded() and Fragment.isDetached() will be changed. These states are preserved on configuration change, which means that (unlike removing a Fragment) it is possible to detach a Fragment but then retrieve it later after configuration change (even if it's not actively being displayed). Even in a detached state, the Fragment runs through all lifecycle methods (except the ones that are related to being attached to an Activity) - for example, onSaveInstanceState() will still be called even if the Fragment is detached (which is quite useful for maintaining state).
FragmentTransaction.show()/hide() - These show or hide a Fragment (in particular, it calls View.setVisibility() on the Fragment's root View). During the transaction the returns of Fragment.isHidden() will be modified. Showing/hiding Fragments states are NOT maintained on configuration change, meaning that if you hide a Fragment it will reappear on config change (for example, if the user rotates the screen).
FragmentTransaction.replace() - Theoretically it is the exact same as removing all Fragments associated with a particular View, then adding a new Fragment in its place. I did not research this much (since I'm more interested in manual control of which Fragments are shown).
Fragment Animations
There are three different ways to animate Fragment transactions. These are ordered by precedence (in other words, the first method, if available, will be used; then the second, then the last).
One thing to note between the compatibility library and the standard Fragment library is that the standard library uses property animators, whereas the compatibility library uses tween Animations.
1. Override your Fragment's onCreateAnimator() method and return an animation. This is the only way to dynamically generate an animation in code (the other two methods rely on pre-configured animations from your resources directory). In the compatibility library, the method is onCreateAnimation() (since the compatibility library uses animations instead of animators).
2. Call FragmentTransaction.setCustomAnimations(), referencing either animators or animations (depending on whether you're using the compatibility library or not). What is interesting is that setCustomAnimations() affects all fragment transitions added to the transaction after it is called. So you need to call setCustomAnimations() before you want it used, and you can actually setup multiple different custom animations for each part of a transaction (with a call to setCustomAnimations() before each add()/remove()/attach()/detach()/show()/hide()/replace()).
3. Setup a style that defines window animations (from this style), then call FragmentTransaction.setTransition() and FragmentTransaction.setTransitionStyle() to specify that style and target the correct animation.
#3 works different in the compatibility library. FragmentTransaction.setTransitionStyle() is completely ignored. Instead, it uses the specified transition to select a stock animation. So if you want custom animations in the compatibility library, you must use #1 or #2.
Tuesday, June 5, 2012
Obscure SimpleDateFormat Android Issue
This bug should win an award for the most obscure bug I've ever run into this year.
You might expect the two formats to be different (one for May 11, the other for May 12). You would be wrong on Android 2.2 and lower. It turns out in this one specific case, the SimpleDateFormat will spit out the same formatted date for the two Calendars.
The conditions seem to be thus:
In this one situation, SimpleDateFormat gets into a state where it formats both Calendars to the same date (May 11). Change any of the above assumptions and the problem will not occur; if the Calendars are more than one day apart, or you use multiple SimpleDateFormats, or you don't set the TimeZone, or you don't immediately reuse the same SimpleDateFormat...
This problem does not occur on Android 2.3+. For Android 2.2 and lower, the solution is simple - create a new SimpleDateFormat when formatting two Calendars one after another.
Check out this code below:
Calendar cal1 = new GregorianCalendar(2012, 5, 11); cal1.setTimeZone(TimeZone.getTimeZone("UTC")); Calendar cal2 = new GregorianCalendar(2012, 5, 12); cal2.setTimeZone(TimeZone.getTimeZone("UTC")); DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); df.setTimeZone(TimeZone.getTimeZone("UTC")); Log.i("test", "cal1.format=" + df.format(cal1.getTime())); Log.i("test", "cal2.format=" + df.format(cal2.getTime()));
You might expect the two formats to be different (one for May 11, the other for May 12). You would be wrong on Android 2.2 and lower. It turns out in this one specific case, the SimpleDateFormat will spit out the same formatted date for the two Calendars.
The conditions seem to be thus:
- Two Calendar objects that are one day apart.
- One SimpleDateFormat that you want to use.
- Both the Calendars and the DateFormat are set to the same TimeZone.
- You format both Calendars using the DateFormat, one after another.
In this one situation, SimpleDateFormat gets into a state where it formats both Calendars to the same date (May 11). Change any of the above assumptions and the problem will not occur; if the Calendars are more than one day apart, or you use multiple SimpleDateFormats, or you don't set the TimeZone, or you don't immediately reuse the same SimpleDateFormat...
This problem does not occur on Android 2.3+. For Android 2.2 and lower, the solution is simple - create a new SimpleDateFormat when formatting two Calendars one after another.
Tuesday, April 17, 2012
Custom Fragment animations are different between the support library and vanilla
I've been converting a lot of my apps to the support library recently and ran into one issue worth mentioning. To be clear: I'm converting apps that use android.app.Fragment to now use support versions of Fragments.
If you do this and you're using FragmentTransaction.setCustomAnimations() you may be in for a shock. Your app will crash in the most glorious fashion:
The reason is that setCustomAnimations() in vanilla use object animators, whereas setCustomAnimations() in the support package use tween animations. It won't give you a compiler error because they're both referenced by resource id.
There's only one solution: convert your object animators to tween animations. The conversion wasn't difficult at all for me. In fact, it makes things a bit easier - object animators don't do percentages while tweens do, a problem I had struggled with before ("how do I get a Fragment to slide in from left to center?").
If you do this and you're using FragmentTransaction.setCustomAnimations() you may be in for a shock. Your app will crash in the most glorious fashion:
FATAL EXCEPTION: main java.lang.RuntimeException: Unknown animation name: objectAnimator at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:124) at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:91) at android.view.animation.AnimationUtils.loadAnimation(AnimationUtils.java:72) at android.support.v4.app.FragmentManagerImpl.loadAnimation(FragmentManager.java:710) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:876) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1080) at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:622) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1416) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:420) at android.os.Handler.handleCallback(Handler.java:587) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:132) at android.app.ActivityThread.main(ActivityThread.java:4123) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:491) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599) at dalvik.system.NativeStart.main(Native Method)
The reason is that setCustomAnimations() in vanilla use object animators, whereas setCustomAnimations() in the support package use tween animations. It won't give you a compiler error because they're both referenced by resource id.
There's only one solution: convert your object animators to tween animations. The conversion wasn't difficult at all for me. In fact, it makes things a bit easier - object animators don't do percentages while tweens do, a problem I had struggled with before ("how do I get a Fragment to slide in from left to center?").
Tuesday, April 10, 2012
Unicode Normalization and Android
I ran into a Unicode rendering issue recently that I wanted to go over. Check out this screenshot from an application we've recently localized into Vietnamese:
Notice how a lot of the text looks cut off (especially those blue labels). What's going on?
It turns out that the TextViews are rendering diacritics improperly. Sometimes the diacritic ends up on the wrong character. Other times, it ends up on its own space. The layout system seems to figure out the width correctly but the renderer screws it up, causing the text to be cut off when layout_width is set to wrap_content. The root of the problem is that Android is messing up combining characters (or combining diacritical marks) in unicode.
The solution that we found was unicode normalization. A character with diacritics can always be represented by a combination of multiple code points, but sometimes there is also a single code point that represents the character. We found that by using unicode's normalization form C (NFC), we could normalize most of the combining diacritical characters out of text (and thus sidestep this problem altogether).
There's two steps we're taking to normalize our text:
1. For all strings that are in our APK, we normalized them inside strings.xml. There are plenty of tools out there (like charlint) that can do the job.
2. We run all strings delivered from servers through Normalizer while parsing. It did not seem to degrade performance to do so.
Step 2 may not be necessary if your app is self-contained and never gets any strings from the net. Also, you could just use Normalizer as your tool for step 1 (it's up to you).
We found this problem to only happen on ICS - it appears to work fine on honeycomb and below. Also, if you want to know more about unicode normalization, I highly recommend reading this article.
Wednesday, April 4, 2012
Yet Another Dumb Widget Hiding Trick
I didn't mean to be writing a series of these articles, but I keep getting backed into a corner...
The last two times, I was dealing solely with preventing app widgets from appearing on particular versions of Android. The problem is that this trick only works for platform version; all other resource qualifiers (like screen size) are ignored.
This becomes a problem when you have a widget that you only want to work on smaller screen sizes, or only in some locales, etc. In this situation, you can't use the AndroidManifest tricks because all those values can change at runtime.
The hackiest answer I found was to manually disable the widget provider on launch, based on whatever parameters you provide. In this case, I'm using a boolean I've defined as "widgetEnabled":
The true answer to this problem is to make sure your widget works on all platforms of Android. But if you're squeezed this hack can save the day as well.
The last two times, I was dealing solely with preventing app widgets from appearing on particular versions of Android. The problem is that this trick only works for platform version; all other resource qualifiers (like screen size) are ignored.
This becomes a problem when you have a widget that you only want to work on smaller screen sizes, or only in some locales, etc. In this situation, you can't use the AndroidManifest tricks because all those values can change at runtime.
The hackiest answer I found was to manually disable the widget provider on launch, based on whatever parameters you provide. In this case, I'm using a boolean I've defined as "widgetEnabled":
public class ExpediaBookingApp extends Application { @Override public void onCreate() { super.onCreate(); if (getResources().getBoolean(R.bool.widgetEnabled)) { PackageManager pm = getPackageManager(); ComponentName cn = new ComponentName(this, AppWidgetProvider.class); pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } } }
This will only disable the widget after the first launch of the application, so it's still possible for users to install the widget before the first launch. But it's way better than nothing. Also note that this doesn't seem to work on every version of Android (but it does work on ICS, which is what I was targeting).
The true answer to this problem is to make sure your widget works on all platforms of Android. But if you're squeezed this hack can save the day as well.
Tuesday, March 27, 2012
Solving Initial Compilation Issues with Android
Two weeks ago I helped with a beginner's Android Workshop at Mobile March. A lot of the people had problems getting their development environment up and running - but for reasons that weren't really any fault of their own, or were easily overlooked. Typically once you've got all the gears in place building is simple, but I'd forgotten how surprisingly difficult it can be to get a project up and running in Eclipse initially.
Here's a few common problems I saw around the classroom:
- Installing the Android SDK (the tools for Android) but not the ADT plugin (how Eclipse interfaces with the SDK), or vice versa. Make sure both are installed.
- Not installing any Android platforms (or not installing the correct one for the app you're trying to build). The SDK is just a starter package - from there, you need to run the Android SDK Manager and add SDK components. There's an SDK platform for each version of Android; I recommend just installing the whole lot (it takes time to download, but makes everything else easy).
- If you get compilation errors upon importing a project, do a full clean and rebuild. Clean is found in Project --> Clean. This can sometimes clear up import issues.
- Compilation issues with @Override. If you're getting compilation error on @Override, that's because your JDK compliance level is set incorrectly. You either need to go into Eclipse --> Preferences --> Java --> Compiler and set the compliance level to 1.6+, or you need to go into the individual project and change that setting (right-click the project --> Properties --> Java Compiler --> Compiler compliance level).
- Using emulators that are not compatible with your sample app. If your app targets 2.3, you must have a 2.3 (or higher) emulator. If your app uses Google Maps, your emulator must be a "Google APIs" emulator (and also conform to the previous version requirement as well).
- Did you try Android briefly more than a year ago and now it refuses to work? Chances are the problem is an invalid debug.keystore. When you build in Eclipse, it creates your APK using an automatically generated debug keystore. Older versions of Android had these set to expire a year after creation, so if you dabbled in Android before it may have expired. You can find the path to your debug.keystore by going into Preferences --> Android --> Build. You can safely delete your old debug.keystore and the Android build process will automatically create a new, valid one for you.
- If none of the above helps, I found the most useful debugging tool for build errors was to turn on verbose output. You can turn that on in Preferences --> Android --> Build --> Build Output, then check the Console for any salient error messages.
Good luck getting your first Android app building!
Here's a few common problems I saw around the classroom:
- Installing the Android SDK (the tools for Android) but not the ADT plugin (how Eclipse interfaces with the SDK), or vice versa. Make sure both are installed.
- Not installing any Android platforms (or not installing the correct one for the app you're trying to build). The SDK is just a starter package - from there, you need to run the Android SDK Manager and add SDK components. There's an SDK platform for each version of Android; I recommend just installing the whole lot (it takes time to download, but makes everything else easy).
- If you get compilation errors upon importing a project, do a full clean and rebuild. Clean is found in Project --> Clean. This can sometimes clear up import issues.
- Compilation issues with @Override. If you're getting compilation error on @Override, that's because your JDK compliance level is set incorrectly. You either need to go into Eclipse --> Preferences --> Java --> Compiler and set the compliance level to 1.6+, or you need to go into the individual project and change that setting (right-click the project --> Properties --> Java Compiler --> Compiler compliance level).
- Using emulators that are not compatible with your sample app. If your app targets 2.3, you must have a 2.3 (or higher) emulator. If your app uses Google Maps, your emulator must be a "Google APIs" emulator (and also conform to the previous version requirement as well).
- Did you try Android briefly more than a year ago and now it refuses to work? Chances are the problem is an invalid debug.keystore. When you build in Eclipse, it creates your APK using an automatically generated debug keystore. Older versions of Android had these set to expire a year after creation, so if you dabbled in Android before it may have expired. You can find the path to your debug.keystore by going into Preferences --> Android --> Build. You can safely delete your old debug.keystore and the Android build process will automatically create a new, valid one for you.
- If none of the above helps, I found the most useful debugging tool for build errors was to turn on verbose output. You can turn that on in Preferences --> Android --> Build --> Build Output, then check the Console for any salient error messages.
Good luck getting your first Android app building!
Thursday, March 1, 2012
Upcoming Talks: AnDevCon III and Mobile March 3D
I'm going to be speaking at AnDevCon III! My talk is called "Don't Make Me Repeat Myself: Tips and Tricks to Avoid Code Duplication and Increase Code Reuse on Android". You can find a long description of it here.
If you register, you can use my last name ("LEW") as a $200 off coupon. This coupon should be valid all the way up till the conference. Also, if you register before March 2nd you get some pretty deep discounts.
I'm slated to give a demo version of this talk at the April AUG.MN meetup (April 3rd), if you happen to be near the Twin Cities.
I thought I'd also mention that I'm going to be briefly demoing Mobiata's apps at Mobile March's 3D event. I'll also be helping out with the Android workshop on Friday. Say hello if you happen to see me around.
If you register, you can use my last name ("LEW") as a $200 off coupon. This coupon should be valid all the way up till the conference. Also, if you register before March 2nd you get some pretty deep discounts.
I'm slated to give a demo version of this talk at the April AUG.MN meetup (April 3rd), if you happen to be near the Twin Cities.
I thought I'd also mention that I'm going to be briefly demoing Mobiata's apps at Mobile March's 3D event. I'll also be helping out with the Android workshop on Friday. Say hello if you happen to see me around.
Thursday, January 26, 2012
Another App Widget Compatibility Trick
I've written previously on the issue of preventing app widgets from appearing on particular versions of Android. This is useful, for example, when you have a Honeycomb-style widget but support pre-Honeycomb devices.
I've got a second version of this trick which I think is a bit easier now. Instead of preventing the system from seeing your AppWidgetProviderInfo, you can actually enable/disable the widget receiver itself based on configuration resource selectors.
First, setup your app widget receiver thus (important part bolded):
Then all you need to do is create a config.xml (in your /res/values/ directory) file that sets "widget_enabled":
You can then leverage the resource qualifier system to enable or disable the widget as necessary for different configurations. For example, if you wanted your widget enabled on all versions of Android except for v11 through v13, you could set it up like this:
/res/values/config.xml <-- widget_enabled=true
/res/values-v11/config.xml <-- widget_enabled=false
/res/values-v14/config.xml <-- widget_enabled=true
The main advantage this provides over my last solution is that you need only modify a bool; the other solution required you to create duplicate versions of the AppWidgetProviderInfo XML file (if you supported, say, v3 through v10 and v14+).
I've got a second version of this trick which I think is a bit easier now. Instead of preventing the system from seeing your AppWidgetProviderInfo, you can actually enable/disable the widget receiver itself based on configuration resource selectors.
First, setup your app widget receiver thus (important part bolded):
<receiver android:enabled="@bool/widget_enabled" android:name="com.company.appwidget.MyAppWidgetProvider" > <intent-filter > <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" /> </receiver>
Then all you need to do is create a config.xml (in your /res/values/ directory) file that sets "widget_enabled":
<resources> <bool name="widget_enabled">true</bool> </resources>
You can then leverage the resource qualifier system to enable or disable the widget as necessary for different configurations. For example, if you wanted your widget enabled on all versions of Android except for v11 through v13, you could set it up like this:
/res/values/config.xml <-- widget_enabled=true
/res/values-v11/config.xml <-- widget_enabled=false
/res/values-v14/config.xml <-- widget_enabled=true
The main advantage this provides over my last solution is that you need only modify a bool; the other solution required you to create duplicate versions of the AppWidgetProviderInfo XML file (if you supported, say, v3 through v10 and v14+).
Tuesday, January 24, 2012
GridLayout Child View Clipping Issues
After back-porting GridLayout to 1.5+, I finally feel comfortable using it in my apps. There's a bit of an initial learning curve, but I think that is mostly a function of un-learning the LinearLayout/RelativeLayout state of mind. I've converted a number of my own layouts to use GridLayout and found them to be much faster/easier to understand afterwards.
There was one particular issue that I ran into that is worth noting, for its solution was not immediately obvious. I was making a two-column GridLayout, where an image was on the left and text was on the right. Here's a simplified version of this layout:
The two Views would align next to each other nicely, but the TextView on the right would end up overflowing off the edge (the example below is not cropped - this is how it appears on the device):
What was happening was that the TextView would take up the entire width of the GridLayout, instead of taking up just the width of the cell (as I expected).
It turns out the solution is to set the width of the TextView to zero, then let the View fill its space dynamically using layout_gravity:
Now the problem is solved:
You need both of the highlighted attributes above; one or the other won't fix the problem. However, it will nicely solve the issue and should be kept in mind anytime you setup a GridLayout child to fill horizontally or vertically.
There was one particular issue that I ran into that is worth noting, for its solution was not immediately obvious. I was making a two-column GridLayout, where an image was on the left and text was on the right. Here's a simplified version of this layout:
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <Space android:layout_width="100dp" /> <TextView android:text="@string/two_cities" /> </GridLayout>
The two Views would align next to each other nicely, but the TextView on the right would end up overflowing off the edge (the example below is not cropped - this is how it appears on the device):
What was happening was that the TextView would take up the entire width of the GridLayout, instead of taking up just the width of the cell (as I expected).
It turns out the solution is to set the width of the TextView to zero, then let the View fill its space dynamically using layout_gravity:
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <Space android:layout_width="100dp" /> <TextView android:layout_width="0dip" android:layout_gravity="fill_horizontal" android:text="@string/two_cities" /> </GridLayout>
Now the problem is solved:
You need both of the highlighted attributes above; one or the other won't fix the problem. However, it will nicely solve the issue and should be kept in mind anytime you setup a GridLayout child to fill horizontally or vertically.
Friday, January 13, 2012
Highlights from Android's New Design Docs
I've just finished reading through the wonderful new Android Design site Google just released. A lot of the information is stuff you should already know if you're an Android developer. However, I took notes of pages/information that I found particularly interesting (or new for ICS). Listed in order of appearance on the site:
48dp Rhythm - Like me, you may have implicitly noticed this pattern before, but it's good to see it stated so explicitly. (Same with the 8dp gap.)
Back vs. Up - Having been confused on the exact distinction for a while, this page is a breath of fresh air.
Action Bar Pattern - This entire page should be required reading for everyone. It definitely looks like the Action Bar is the future, and this is the instruction manual.
Long Touch Multiselect - Breaking news: long touch no longer activates the contextual menu. Now, it is purely for activating selection (aka multiselect). Every longtime Android developer should be aware of this change.
(Personally, I didn't even notice this change despite owning a Galaxy Nexus for a month. Perhaps that's because not even every Google app has switched to the new design yet. Or perhaps it's because long touch is just not an intuitive way to design any functionality - I've long since given up putting any core functionality into long touch. Still, it's worth noting the design shift.)
Android is not the iPhone - At least, that's what this section *should* be called. This page will be most useful for showing to your designer or boss how Android differs from iPhone and why you shouldn't just port assets and design directly from one to the other.
48dp Rhythm - Like me, you may have implicitly noticed this pattern before, but it's good to see it stated so explicitly. (Same with the 8dp gap.)
Back vs. Up - Having been confused on the exact distinction for a while, this page is a breath of fresh air.
Action Bar Pattern - This entire page should be required reading for everyone. It definitely looks like the Action Bar is the future, and this is the instruction manual.
Long Touch Multiselect - Breaking news: long touch no longer activates the contextual menu. Now, it is purely for activating selection (aka multiselect). Every longtime Android developer should be aware of this change.
(Personally, I didn't even notice this change despite owning a Galaxy Nexus for a month. Perhaps that's because not even every Google app has switched to the new design yet. Or perhaps it's because long touch is just not an intuitive way to design any functionality - I've long since given up putting any core functionality into long touch. Still, it's worth noting the design shift.)
Android is not the iPhone - At least, that's what this section *should* be called. This page will be most useful for showing to your designer or boss how Android differs from iPhone and why you shouldn't just port assets and design directly from one to the other.
Tuesday, January 10, 2012
GridLayout Library for Android 1.5+ Support
If you're like me, when you first heard about GridLayout, you were excited. Then disappointed, because it's currently only available for Ice Cream Sandwich. That is true no longer - I've taken the time to port a working version of it all the way back to Android 1.5 and above.
Check out the android-gridlayout library here.
The one caveat is that there is one method in GridLayout that I was not able to port back. As a result, when you change the visibility of a child View of a compatibility GridLayout, you should also call GridLayout.notifyChildVisibilityChanged().
Let me know what you think, especially if there are any bugs.
Check out the android-gridlayout library here.
The one caveat is that there is one method in GridLayout that I was not able to port back. As a result, when you change the visibility of a child View of a compatibility GridLayout, you should also call GridLayout.notifyChildVisibilityChanged().
Let me know what you think, especially if there are any bugs.
Subscribe to:
Posts (Atom)