Skip to content Skip to sidebar Skip to footer

Overriding Resources At Runtime

The problem I would like to be able to override my apps resources such as R.colour.brand_colour or R.drawable.ic_action_start at runtime. My application connects to a CMS system t

Solution 1:

While "dynamically overriding resources" might seem the straightforward solution to your problem, I believe a cleaner approach would be to use the official data binding implementation https://developer.android.com/tools/data-binding/guide.html since it doesn't imply hacking the android way.

You could pass your branding settings using a POJO. Instead of using static styles like @color/button_color you could write @{brandingConfig.buttonColor} and bind your views with the desired values. With a proper activity hierarchy, it shouldn't add too much boilerplate.

This also gives you the ability to change more complex elements on your layout, i.e.: including different layouts on other layout depending on the branding settings, making your UI highly configurable without too much effort.

Solution 2:

Having basically the same issue as Luke Sleeman, I had a look at how the LayoutInflater is creating the views when parsing the XML layout files. I focused on checking why the string resources assigned to the text attribute of TextViews inside the layout are not overwritten by my Resources object returned by a custom ContextWrapper. At the same time, the strings are overwritten as expected when setting the text or hint programatically through TextView.setText() or TextView.setHint(). This is how the text is received as a CharSequence inside the constructor of the TextView (sdk v 23.0.1):

// android.widget.TextView.java, line 973
text = a.getText(attr);

where a is a TypedArray obtained earlier:

// android.widget.TextView.java, line 721
 a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);

The Theme.obtainStyledAttributes() method calls a native method on the AssetManager:

// android.content.res.Resources.java line 1593publicTypedArrayobtainStyledAttributes(AttributeSet set,
            @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
...
        AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
                parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);

...

And this is the declaration of the AssetManager.applyStyle() method:

// android.content.res.AssetManager.java, line 746/*package*/nativestaticfinalbooleanapplyStyle(long theme,
        int defStyleAttr, int defStyleRes, long xmlParser,
        int[] inAttrs, int[] outValues, int[] outIndices);

In conclusion, even though the LayoutInflater is using the correct extended context, when inflating the XML layouts and creating the views, the methods Resources.getText() (on the resources returned by the custom ContextWrapper) are never called to get the strings for the text attribute, because the constructor of the TextView is using the AssetManager directly to load the resources for the attributes. The same might be valid for other views and attributes.

Solution 3:

After searching for a quite long time I finally found an excellent solution.

protectedvoid redefineStringResourceId(finalString resourceName, finalint newId) {
        try {
            final Field field = R.string.class.getDeclaredField(resourceName);
            field.setAccessible(true);
            field.set(null, newId);
        } catch (Exception e) {
            Log.e(getClass().getName(), "Couldn't redefine resource id", e);
        }
    }

For a sample test,

private Object initialStringValue() {
                // TODO Auto-generated method stubreturn getString(R.string.initial_value);
            }

And inside the Main activity,

before.setText(getString(R.string.before, initialStringValue()));

            finalString resourceName = getResources().getResourceEntryName(R.string.initial_value);
            redefineStringResourceId(resourceName, R.string.evil_value);

            after.setText(getString(R.string.after, initialStringValue()));

This solution was originally posted by, Roman Zhilich

ResourceHackActivity

Post a Comment for "Overriding Resources At Runtime"