4

I've implemented a custom seekbar preference, using this site - seekbar preference and it works just fine. Now I want to add values to the seekbar's customized properties from the string.xml file instead of hard-coding them:
Instead of writing customseekbar:unitsRight="Seconds" I want to have a string resource like <string name="units">Seconds</string> and use it like this: customseekbar:unitsRight="@string/units". I've tried to implement this guide. My relevant code is:
attrs.xml

<resources>
<declare-styleable name="CustomSeekBarPreference">
    <attr name="unitsRight" format="reference|string"/>
</declare-styleable>

And the constructor -
CustomSeekBarPreference.java

public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {

    private static final String APPLICATIONNS="http://CustomSeekBarPreference.com";
    public CustomSeekBarPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(
            attrs, R.styleable.CustomSeekBarPreference, 0 ,0);
        mUnitsRight = a.getString(R.styleable.CustomSeekBarPreference_unitsRight);
        a.recycle();
}

and the layout -

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:customseekbar="http://CustomSeekBarPreference.com" >

<com.x.sharedpreferencestestapp.CustomSeekBarPreference
    android:key="1"
    android:defaultValue="30"
    android:max="100"
    customseekbar:min="0"
    android:title="Default step"
    customseekbar:unitsRight="@string/units"/>
</PreferenceScreen>

But as you can see, I get 'null' instead of the right value: screenshot Even if I change the value to a fixed string instead of string resource, like customseekbar:unitsRight="Seconds" I still get the same result. And just to make it clear - if I stick to the original code of the seekbar preference: mUnitsRight = getAttributeStringValue(attrs, APPLICATIONNS, "unitsRight", "defaultValue") it works, but not with string resource.

12
  • You've got the wrong namespace in the layout. Change it to xmlns:customseekbar="http://schemas.android.com/apk/res-auto". Commented Oct 11, 2017 at 13:27
  • @MikeM. If I change the name space I get an error - since I have other custom attributes, like min, I get - No resource identifier found for attribute 'min' in package 'com.x.sharedpreferencestestapp'. Does it mean that I have to add a third namespace (and so - how?) or that I must implement values at the attrs.xml for all the customed properties? Commented Oct 11, 2017 at 13:38
  • Nah, you don't need to define a third one. You'll just need to add your custom attributes to your <declare-styleable>. That example is rather odd, in that it's kinda handling the XML attributes "raw". The reason getAttributeStringValue() works is because it's specifying its own namespace in retrieving the attributes' values. When you use obtainStyledAttributes(), it's using a particular namespace that is basically "http://schemas.android.com/apk/res/" + packageName (res-auto is a shortcut), so it won't pull any attributes with your "http://CustomSeekBarPreference.com" namespace. Commented Oct 11, 2017 at 13:50
  • Alternatively, I suppose you could modify the getAttributeStringValue() method to handle string resource references, too, and keep the current setup, but I'd have to do a little research to be sure how that's done, exactly. Commented Oct 11, 2017 at 13:56
  • @MikeM. Ok, that makes sense. Can you please add your first 2 comments to an answer? Commented Oct 11, 2017 at 13:58

1 Answer 1

3

You're getting null for that attribute value because of the namespace you've declared for it in the layout XML - http://CustomSeekBarPreference.com.

As far as I'm aware, the obtainStyledAttributes() method can only pull attributes that are in the standard Android resource namespace – http://schemas.android.com/apk/res/android – or your app's resource namespace, which is http://schemas.android.com/apk/res/ plus the app's package name. Attributes in any other namespace will be ignored, and will not be in the returned TypedArray, which is why you get null no matter if the value is a hardcoded string, or a resource reference.

In the example you're following, they've used a similar non-standard namespace, but they're pulling the value directly from the AttributeSet, specifying that namespace in the getAttributeValue() call thereon. I can't say that I've seen this particular method often used in this manner, and the only benefit I can see to it is that it saves you from having to define your own custom attributes, which is a rather trivial task.

There are a couple of ways to fix this.


  • Move those layout attributes into your app's namespace, and define an attr resource for each.

    This is the method demonstrated in the developer page you've linked, and is probably the most common and familiar way to implement custom View attributes.

    First change the namespace declaration in the layout to:

    xmlns:customseekbar="http://schemas.android.com/apk/res-auto‌​"
    

    (The res-auto segment is a convenience that will cause the actual namespace name to be appropriately constructed with the current package name, as previously described.)

    With your posted setup, this will then cause an error with customseekbar:min, since you've not defined min as an attribute resource (attr) in your app. You can simply define that attr, and then handle it the same way you're handling unitsRight; i.e., retrieve its value from the TypedArray returned from obtainStyledAttributes(). You would do the same for any additional custom attributes you might need.


  • Modify the getAttributeStringValue() method in the CustomSeekBarPreference example to handle resource references as well.

    This may be the simpler option, as far as modifying the given example, but directly accessing the AttributeSet values prevents those values from being adjusted for any theme or style that you might wish to apply. If that's not a concern, then the necessary changes are rather simple.

    In the modified method, we just need to first check if the attribute value is a resource value, using AttributeSet#getAttributeResourceValue(). If that method returns a valid identifier, we retrieve the actual value with Resources#getString(). If not, we treat the attribute value as a plain string.

    private String getAttributeStringValue(AttributeSet attrs, String namespace,
                                           String name, String defaultValue) {
    
        String value = null;
        int resId = attrs.getAttributeResourceValue(namespace, name, 0);
    
        if (resId == 0) {
            value = attrs.getAttributeValue(namespace, name);
            if (value == null)
                value = defaultValue;
        }
        else {
            value = getContext().getResources().getString(resId);
        }
    
        return value;
    }
    

    Using this method, you would not need to define your custom attributes, and the layout namespace can remain as you have it in the posted snippet.

Sign up to request clarification or add additional context in comments.

1 Comment

I've used the first method that you've suggested and it's working fine. Thank you for the detailed answer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.