zbr
zbr

Reputation: 7037

Why does casting net.simonvt.numberpicker.NumberPicker to android.widget.NumberPicker work?

I have an app using NumberPickers. Since the app supports older versions of Android, where no NumberPicker widget had been available yet, I have to use an external library.

In my XML layout numberpicker_dialog.xml, I define the NumberPicker like this:

<net.simonvt.numberpicker.NumberPicker
    android:id="@+id/interval_picker_1"
    ... />

Then in the code of my activity, I have:

import net.simonvt.numberpicker.NumberPicker;

private View intervalPickerDialogBody;
private NumberPicker intervalPicker1;
private android.widget.NumberPicker api11_intervalPicker1;

protected void onCreate(Bundle savedInstanceState) {

    intervalPickerDialogBody = getLayoutInflater()
            .inflate(R.layout.numberpicker_dialog, null);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        // the casting in question happens here
        api11_intervalPicker1 = (android.widget.NumberPicker)
                 intervalPickerDialogBody.findViewById(R.id.interval_picker_1);
    } else {
        intervalPicker1 = (NumberPicker)
                 intervalPickerDialogBody.findViewById(R.id.interval_picker_1);
    }

}

It works, and really on Honeycomb and newer, android.widget.NumberPicker is used, on older versions, net.simonvt.numberpicker.NumberPicker is used.

But now that I am writing a thesis about the app using this, I wonder:

  1. Why does casting net.simonvt.numberpicker.NumberPicker from the XML layout to android.widget.NumberPicker work when they in reality are different classes?
  2. How come that only this casting makes the device use android.widget.NumberPicker on Honeycomb and newer when the NumberPicker that's in the XML layout is actually the one from the exetrnal library? When is an instance of android.widget.NumberPicker ever created?

The fact that android.widget.NumberPicker is used on Honeycomb and newer is supported by the fact the second line of the forecoming code snippet equals true on my 4.0.3 phone.

View tmp = intervalPickerDialogBody.findViewById(R.id.interval_picker_1);
tmp instanceof android.widget.NumberPicker // pseudocode

(Thank you, Jules, this is a much better proof than the striked one with the arrows. :))

If anyone wonders how I know that android.widget.NumberPicker is actually used on Honeycomb and newer, it's because net.simonvt.numberpicker.NumberPicker doesn't display arrows above and below the NumberPicker as it was backported from 4.2 where the arrows have been removed. And when I try it on my device running 4.0.3, the arrows are there even though if it should display net.simonvt.numberpicker.NumberPicker, they wouldn't be.

I hope the question is understandable. Thanks for elaborating on the subject.

Upvotes: 1

Views: 1141

Answers (1)

Jules
Jules

Reputation: 15199

Interesting question. It seems to be because of what appears to me to be a bug in LayoutInflater. Here's the code used to decide what View class is used for a particular XML tag:

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor constructor = sConstructorMap.get(name);
    Class clazz = null;

    try {
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            sConstructorMap.put(name, constructor);
        }
        ...

Before this is called, the prefix (i.e. everything up to and including the last dot in the tag name) is separated out and put into the prefix parameter, so for your XML, the call is:

createView ("NumberPicker", "net.simonvt.numberpicker.", ...);

This means that when the createView method checks in the cache for a previously-used constructor, as long as a NumberPicker has been used before it will return the system NumberPicker.

As a corollary to my belief that this is a bug, I would modify your code above to safeguard against the possibility that a future variant of Android will fix the bug by testing which type of object is returned by findViewById using the instanceof operator, rather than your current version number test, i.e.:

View tmp = intervalPickerDialogBody.findViewById(R.id.interval_picker_1);
if (tmp instanceof android.widget.NumberPicker) { // Note 1 below
    api11_intervalPicker1 = (android.widget.NumberPicker)
             intervalPickerDialogBody.findViewById(R.id.interval_picker_1);
} else {
    intervalPicker1 = (NumberPicker)
             intervalPickerDialogBody.findViewById(R.id.interval_picker_1);
}

Note 1 is that I'm making the assumption that the instanceof check won't crash at run time if the class you're checking against doesn't exist. If it does, you'll have to reverse the order of the checks.

Upvotes: 1

Related Questions