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:

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":

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.