Search code examples
androidtypesandroid-appcompatpreference

How does android.support break Javas type safety?


Today i encountered a very strange behavior. I know, I'm using deprecated APIs but nonetheless, this should not be possible from my understanding.

I have a android.preference.PreferenceActivityin which I put the following xml via addPreferenceFromResource:

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <EditTextPreference
        android:id="@+id/et_server_endpoint"
        android:key="server_endpoint"
        android:hint="http://192.168.100.42:8080"
        android:title="Server Endpoint"
        android:summary="Sets the Server Endpoint for the search"
        android:dialogTitle="Server Endpoint Settings"
        android:dialogMessage="Set the Servers endpoint"
        />
    <SeekBarPreference
        android:id="@+id/sb_phone_threshold"
        android:key="match_treshold"
        android:title="Matching Threshold"
        android:summary="Sets the minimum Score for matches"
        android:defaultValue="60"
        android:min="40"
        android:max="90"
        app:adjustable="true"
        app:showSeekBarValue="true"/>  
</PreferenceScreen>

Note that SeekBarPreference is an android.support.v7.preference.Preference

I have no problem inflating the xml. The Screen shows as expected. The problem comes when I want to get a reference to it:

PreferenceActivity.findPreference(key) is supposed to return an android.preference.Preference class (which the SeekBarPreference is not).

but this code returns a valid Pref:

 Preference match_treshold = findPreference("match_treshold");

I'm not allowed to cast it:

 if (match_treshold instanceof SeekBarPreference){}

because of

error: incompatible types: Preference cannot be converted to SeekBarPreference

But surprisingly, if I debug the code I get this Debugging SeekBarPreference

It states, that the class is android.preference.SeekbarPreference. If I google that, all I can find is https://developer.android.com/reference/android/support/v7/preference/SeekBarPreference

which states that it's based on android.preference.SeekBarPreference. But I cannot find this specific class.

Beside the - from my point of view - poor design decision, to not inherit support.v7 Preferences from the Android ones: What the hell is going on there?

EDIT: Due to request I post the Activity class

package com.my.company.domain.namespace;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.preference.SeekBarPreference;
import android.util.Log;

public class PrefActivity extends PreferenceActivity {

    private SharedPreferences mPrefs;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        addPreferencesFromResource(R.xml.glass_prefs);
        Preference matchTreshold = findPreference("match_treshold");
//        code doesn't compile
//        if (match_treshold instanceof SeekBarPreference){}
        Log.d("Pref", "onCreate: SeekBarClass=" + matchTreshold.getClass().getName());
        //prints 'android.preference.SeekBarPreference'
        //one cannot import android.preference.SeekBarPreference
    }
}

you need to add

implementation 'com.android.support:preference-v7:28.0.0'

to the build.gradle deps


Solution

  • The SeekBarPreference object that you end up with in an android.preference.PreferenceActivity is indeed an android.preference.SeekBarPreference. The issue here is that that class is hidden in the SDK (source), so Android Studio will not suggest it for auto-completion, nor will it import it automatically. Since you're using the support libraries in your project, and the support version of SeekBarPreference is publicly available, that will be the class Android Studio tries to use there, thus the conflict.

    Unfortunately, PreferenceActivity does not check from where the preference XML is coming, so it will happily create an instance of that platform class whenever it sees a <SeekBarPreference> tag. This is arguably a bug, but I'm not sure they'd consider it so, as that class is nowhere mentioned in the documentation.

    You possibly could still use the platform SeekBarPreference, as it should work like any other basic Preference, though it might get a little hairy if you need to manipulate it directly. However, writing your own, as you've mentioned, is certainly a more solid, accessible solution. The platform class (linked above) is a rather simple Preference subclass, so you could even just copy that out into your project, almost as is. The relevant layouts (here and here) are similarly straightforward, and don't rely on anything else hidden from the SDK.