87

Let's say, on my API call I have a parameter that's called color. Is it possible to edit or modify an existent R.colors.color to assign the color from the API result?

As an example:

I make a call to my API and it returns green, now I want to load my app with i.e (green Toolbar, green TextView color, etc.), is that possible?

My first thought was:

Create a item on colors.xml called demo then assign it a default color, then use this demo color wherever I want (Button, TextView, etc.) Then I thought it could be possible to change this value programmatically with the result from the API so I wouldn't need to create a SharedPreferences or something like that and for avoiding more code.

As @Y.S. said to me

Unfortunately, you WILL have to set the color of the text or view manually everywhere ... :(

I would like if there is other way to do it, since I don't know how many Activities my project will contain, so if is there other way to do it I'm glad to hear other guesses.

EDIT

I'm trying the @Jared Rummler answer and maybe i'm doing something wrong... I've created a simple Json and I put on my Assets I parse the Json and I put it on a GlobalConstant then I made a "simple app".

First of all I have a TextView and a Button which contains the "your_special_color", and the return of it I put the GlobalConstant int as follows :

case "your_special_color":                
            return GlobalConstant.color; 

Then what I tried is my first Activity has 1 TextView and 1 Button as I said before and they have the color "your_special_color" that I don't want to change it, BUT I have an Intent on my Button to open the other Activity that contains the same but with the GlobalConstant.color and it doesn't change.

I tried it doing this (my second Activity):

public class Main2Activity extends AppCompatActivity {
private Res res;
@Override public Resources getResources() {
    if (res == null) {
        res = new Res(super.getResources());
    }
    return res;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
}

Did I miss something?

Oh.. I figured it out I guess is doing this on my MainActivity2 ?

 Button btn = (Button)findViewById(R.id.button2);
 btn.setBackgroundColor(res.getColor(R.color.your_special_color));
Community
  • 1
  • 1
Skizo-ozᴉʞS ツ
  • 19,464
  • 18
  • 81
  • 148

5 Answers5

81

You can create a class which extends Resources and override the methods getColor(int) and getColor(int, Theme).

Example:

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="your_special_color">#FF0099CC</color>
</resources>

Res.java

public class Res extends Resources {

    public Res(Resources original) {
        super(original.getAssets(), original.getDisplayMetrics(), original.getConfiguration());
    }

    @Override public int getColor(int id) throws NotFoundException {
        return getColor(id, null);
    }

    @Override public int getColor(int id, Theme theme) throws NotFoundException {
        switch (getResourceEntryName(id)) {
            case "your_special_color":
                // You can change the return value to an instance field that loads from SharedPreferences.
                return Color.RED; // used as an example. Change as needed.
            default:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    return super.getColor(id, theme);
                }
                return super.getColor(id);
        }
    }
}

BaseActivity.java

public class BaseActivity extends AppCompatActivity {

    ...

    private Res res;

    @Override public Resources getResources() {
        if (res == null) {
            res = new Res(super.getResources());
        }
        return res;
    }

    ...

}

This is the approach I have used in one of my apps, Root Check. If you override getResources in your activities and main application class you can change the theme programmatically (even though themes are immutable). If you want, download the app and see how you can set the primary, accent, and background colors from preferences.

Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
  • thanks for your answer guy! I'll read it carefully and try to see if it's what I need, with this you think if I get a color (int) from an API call and I use on all of my backgrounds, styles, etc... my default color it will show the color of the API? – Skizo-ozᴉʞS ツ Dec 09 '15 at 12:37
  • I've downloaded it, I used it before didnd't know the dev of this app were here :P, amazing animation on the start... and yes, I saw how you change the colors but will it turn black the screen for a seconds? I only need to do this at the start of the APP... – Skizo-ozᴉʞS ツ Dec 09 '15 at 12:58
  • @JaredRummler: I don't get it ... how does overriding `getColor()` help change the fixed values of resource elements at runtime ? – Yash Sampat Dec 09 '15 at 20:13
  • @Y.S. If you look at the code, it is checking the resource name. If it is a certain id, you can have `getColor` return a different color value. You could also do this for `getDrawable` or even `getString`. – Jared Rummler Dec 09 '15 at 22:30
  • @JaredRummler: true, but don't you still have to call getColor() for different views/widgets ? – Yash Sampat Dec 10 '15 at 04:52
  • @JaredRummler I mean I can use this : return Color.RED; as a result of my int color from api call? – Skizo-ozᴉʞS ツ Dec 10 '15 at 10:53
  • @Skizo, if you follow my answer then `getResources().getColor(R.color.your_special_color)` will return red instead of what you set in `res/colors.xml`. If you want, I could probably publish a small example to GitHub. Your question is a little ambiguous. You want a color resource to return a different value you set programmatically, correct? – Jared Rummler Dec 10 '15 at 10:57
  • Well yes, I think you got it because it's pretty simple, first I created my app with a `default_value` and I used it on whole app (`ActionBar background`, `textColor`, etc...) So now, I want that if I get from my API the color `Purple` (for example), the `default_value` turn into `purple`, and I'm wondering if I'll have to do it for each view on my app... Because as Y.S. said to me, when you compile the APP it compiles the R class then the color is on it.... – Skizo-ozᴉʞS ツ Dec 10 '15 at 11:01
  • All views will use `getResources` from your `Activity` to get the color unless it uses a custom `ContextThemeWrapper` (from my experience this is only used in `AlertDialog`). Just follow the steps in the answer or I can publish a sample. I was thinking of open sourcing the theme change library I'm using in my app. – Jared Rummler Dec 10 '15 at 11:05
  • But let's say I have my styles with my custom color (toolbar, tabs, etc..) and my text color from my buttons, must I have to getResources from all of those widgets? @JaredRummler – Skizo-ozᴉʞS ツ Dec 13 '15 at 14:32
  • @Skizo, I read your comments. It's hard to understand exactly what you are looking to do. Did you try out my answer? – Jared Rummler Dec 14 '15 at 21:38
  • @JaredRummler I'll try to explain it better... from my API i get a "red" color, and on MainActivity I get this "red" color, from here all right? ok, then on my APP I used to use "default" color for all of my widgets/views, and I would like to change the "default" to "red" (HEX, obviously). – Skizo-ozᴉʞS ツ Dec 14 '15 at 21:40
  • I meant if I get the int from an API call wich is called on MainActivity how can I `return Color.RED;` return the correct color? – Skizo-ozᴉʞS ツ Dec 15 '15 at 11:49
  • 3
    I won't be available for another 6 hours. Feel free to email me and I'll help you out. jared.rummler@gmail.com – Jared Rummler Dec 15 '15 at 21:12
  • @Skizo I'll reply later tonight – Jared Rummler Dec 18 '15 at 03:36
  • @JaredRummler I have overridden the method in my activity and application. I have `@color/colorAccent`, in my theme. I use `?attr/colorAccent` in xml, but it has no effect. I have overriden for case:"colorAccent" in the method you asked – q126y Dec 05 '16 at 08:43
  • 11
    Sorry guys, but this is now `DEPECRATED` – Arav K. Jan 15 '17 at 08:52
  • @JaredRummler can you send me sample code for change color of theme? my email divyesh_asd@ymail.com – Divyesh Patel Mar 15 '17 at 07:22
  • @JaredRummler any alternative way? Now it has been deprecated – Jimit Patel Jun 20 '17 at 11:46
  • 1
    getColor(int, Theme) not calling – learner Jul 27 '17 at 06:58
  • can u pls send me with example for this color changes. – saiRam89 Aug 30 '17 at 08:26
  • 2
    I wrote a library that dynamically changes the background, primary and accent colors of an app. The library is currently not open but hopefully I can share it soon. The code above will not work on Android 6.0+ because many changes were made to Resources. Also, I no longer own the Root Check app linked in the answer. – Jared Rummler Aug 31 '17 at 04:32
  • Hello, does this method still work? I think it looks promising and I am trying to test it for intercepting `getColor(int id, Theme theme)`. If I understand correctly, does "your_special_color" correspond to a color name in `colors.xml`? Also, does the returned color (in the example, Color.Red), have to be a hardcoded value somewhere in colors.xml? I'm trying to change theme color based on a custom user choice. Any chance of replacing the ResourceEntryName's hex value perhaps? – Erlend K.H. Jan 08 '19 at 20:04
  • 1
    Hi Guys, the approach @JaredRummler has suggested works but seems only when the color is applied programmaticaly. Something like this: ``` val colorId: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { resources.getColor(R.color.appBackground, theme) } else { resources.getColor(R.color.appBackground) } contentMain.setBackgroundColor(colorId) ``` The color applied in the XML is not queried by the method ` resources.getColor(...)`. @JaredRummler please correct me if I am wrong. I have tested in on Android 10. – Neernay Apr 02 '20 at 12:47
  • @JaredRummler can you share your way of how to change colours in a library when passed from parent application? – Abhinav Tyagi May 29 '20 at 20:04
30

If you take a look at the Accessing Resources document, what it says is that ...

Once you provide a resource in your application, you can apply it by referencing its resource ID. All resource IDs are defined in your project's R class, which the aapt tool automatically generates.

Furthermore,

When your application is compiled, aapt generates the R class, which contains resource IDs for all the resources in your res/ directory. For each type of resource, there is an R subclass (for example, R.drawable for all drawable resources), and for each resource of that type, there is a static integer (for example, R.drawable.icon). This integer is the resource ID that you can use to retrieve your resource.

What this is saying, essentially, is that pretty much everything held as a resource in the res/ directory is compiled and referenced as an unchangeable constant. It is for this reason that the values of resource elements cannot be changed programmatically/at runtime, because they are compiled. As opposed to local/global variables & SharedPreferences, resource elements are represented in program memory as fixed, unchangeable objects. They are held in a special read-only region of program memory. In this regard, see also Changing value of R.String Programmatically.

What you can do is, to avoid using the same code at a thousand places in your project, create a common function that changes the value of the color in the SharedPreferences and use this method everywhere. I'm sure you knew this already, of course.

To reduce the amount of code you need to add to the project, there is an alternative. I have previously used the calligraphy library which allowed me to fix the font style & color throughout the app. This may be of some good use to you, check it out ...

Community
  • 1
  • 1
Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
  • So you are saying to me that its not possible to do that...? Then I'll have to create on each fragment on each activity the color to the view? – Skizo-ozᴉʞS ツ Nov 30 '15 at 09:29
  • That's right, you can't do that. But you don't have to create the color repeatedly, you can create it in a common base class and keep the value as a global variable that you can use everywhere. Unfortunately, you **WILL** have to set the color of the text or view manually everywhere ... :( – Yash Sampat Nov 30 '15 at 09:44
  • Yes that what I meant... I can save the value on sharedpref then create a globalclaas to declare the color but then... All of the setcolor set background are needed well... I'll try to find another solution somewhere if I don't find any I'll do what you say @Y.S. – Skizo-ozᴉʞS ツ Nov 30 '15 at 09:48
  • With this from de api i can get a font aswell? – Skizo-ozᴉʞS ツ Nov 30 '15 at 10:27
  • Do you mean that I can assign a font to the app and then declare the color? – Skizo-ozᴉʞS ツ Nov 30 '15 at 11:17
  • See the use of the calligraphy library ... it lets you see the custom font in XML (this is not possible by default in Android). And it lets you set the fonts in an `Activity` without calling `setTypeFace()` everywhere. This may help make your work easier. – Yash Sampat Nov 30 '15 at 11:35
  • I started a bounty to see if it have other way to do it... otherwise, the bounty is for you ;) – Skizo-ozᴉʞS ツ Dec 08 '15 at 16:42
  • see the answer that @Jared Rummler gave to me, it's that good? – Skizo-ozᴉʞS ツ Dec 09 '15 at 12:38
  • Its an interesting idea, though at first glance I can't tell how it helps solve this problem ... :P – Yash Sampat Dec 09 '15 at 20:14
  • I'll test it, I saw that you've posted a comment in the answer I was wondering the same... let's see if he replays to your question :) – Skizo-ozᴉʞS ツ Dec 09 '15 at 21:23
  • Did you hear about variant build? – Skizo-ozᴉʞS ツ Dec 13 '15 at 14:33
  • I guess you mean build variant ... Yes, that is an approach, but that is not the same as changing values at runtime ... – Yash Sampat Dec 13 '15 at 20:44
  • Yo master @Y.S. if you have time let me know if you have any clue about [this](http://stackoverflow.com/questions/34889264/recyclerview-add-emptyview/34889332?noredirect=1#comment57517283_34889332) – Skizo-ozᴉʞS ツ Jan 20 '16 at 01:24
  • Hey Skizo ... Of course I will see that question ... :) Sorry for not replying to your last comment ... Do send me a reminder today evening if I forget ... Will definitely reply ... :) – Yash Sampat Jan 20 '16 at 04:54
  • Okay thanks, i think we together can solve the issue :) – Skizo-ozᴉʞS ツ Jan 20 '16 at 06:10
  • Sorry for the delay, Skizo ... I'm looking at that question now ... :) – Yash Sampat Jan 22 '16 at 04:47
  • how long can you try to help me out on this [issue](https://stackoverflow.com/questions/46778920/move-an-imageview-on-screen-like-dragging)? :D – Skizo-ozᴉʞS ツ Oct 16 '17 at 20:54
  • Check this [question](https://stackoverflow.com/questions/46958600/create-service-to-detect-any-action-from-the-user) it's about services please :D – StuartDTO Nov 01 '17 at 13:25
  • @Y.S have a question with bounty perhaps you can guide me how to, read the chat if you want for the answer answered already https://stackoverflow.com/questions/58376131/drag-and-drop-imageview-into-a-container-for-verification I know you can earn it! :D – Skizo-ozᴉʞS ツ Oct 21 '19 at 07:21
17

R class is not supposed to be edited. It merely contains references to your resources.

You will need to set it manually. However, to reduce the burden of setting it manually you can try to use special libraries for preference saving, for instance:

(full list of similar libraries https://android-arsenal.com/tag/75)


Also, you might want to think about another way of applying styles and passing parameters - consider you would want to add some other parameters like height, width etc. For that purpose, you can define custom attribute in themes.xml/styles.xml:

<attr name="demoColor" format="reference|color" />

then define styles:

<style name="BaseActivity">
</style>
<style name="GreenActivity" parent="@style/BaseActivity">
    <item name="demoColor">#00cd00</item>
</style>
<style name="RedActivity" parent="@style/BaseActivity">
    <item name="demoColor">#ff0000</item>
</style>

then use that color in your xml like this:

... android:background="?demoColor" ...

and switch between GreenActivity and RedActivity styles in Activity.onCreate:

setTheme(isGreenStyle() ? R.style.GreenActivity : R.style.RedActivity)
setContentView(...)

With the above approach, you will be able to easily configure your styles in xml and it should be less code and easier to refactor in future. (You will still need to have one variable in preference to save whether you have green or red style)


Another way, if you want to show demos of your app with different colors is to use build variants / flavors for loading your app with different colors and styles (it is for build time - not runtime):

app/src/main/res/colors.xml

<resources>
    <color name="demoColor">#00cd00</color>
</resources>

app/src/buildVariant/res/colors.xml

<resources>
    <color name="demoColor">#ff0000</color>
</resources>

Now you can quickly switch between "main" and "buildVariant" in Build Variants menu and launch your app with different "demo" colors. The same way you can customize a lot of other attributes.

Search for "Build Variants" here http://developer.android.com/tools/building/configuring-gradle.html

GregoryK
  • 3,011
  • 1
  • 27
  • 26
  • Oh didn't hear about this... So I can change the value programmatically if I use the buildvariant? – Skizo-ozᴉʞS ツ Dec 13 '15 at 14:30
  • @Skizo build variants / flavors are not for changing the value programmatically - they allow you to build and run your app with different styles/colors/resources. It seems I missed "programmatically" part from your questions :) I will update my answer a bit. It would help if you could specify real use of the functionality you are trying to implement. Maybe there's a better way to do it. – GregoryK Dec 14 '15 at 09:09
  • Well I just want to save the color, not the style. – Skizo-ozᴉʞS ツ Dec 14 '15 at 21:43
  • I see. Added a couple of preference libraries, so you might consider using them as they allow to bind preferences - this might help with reducing code. – GregoryK Dec 15 '15 at 10:54
  • 1
    This is exactly what I was looking for!!! You are a genius! It gives you the possibility to use Colors in layouts and in code as well. Here is a good example to use it in code: http://stackoverflow.com/questions/17277618/get-color-value-programmatically-when-its-a-reference-theme – bentzy Mar 29 '16 at 15:18
  • 1
    your answer and this code : ?demoColor , help me lot . thats i want . – Adnan Abdollah Zaki Jan 02 '17 at 13:21
10

You can't change an app's resources, they are all constants. Instead you can save your color in SharedPrefences and use the color from there.

See How to use SharedPreferences in Android to store, fetch and edit values.

If your app already has a R.color.green defined and you just want to access it based on what API returned you use:

int resourceID = getResources().getIdentifier("green", "color", getPackageName());
Community
  • 1
  • 1
Sourabh
  • 8,243
  • 10
  • 52
  • 98
  • I know about `SharedPreferences` and I think you didn't get me, I meant that you can create a file called `colors.xml` right? and then on it create items with colors with HEX, then I would like to know if I can moddify this value programmatically to first put the demo color and then the real color. – Skizo-ozᴉʞS ツ Nov 29 '15 at 20:32
  • You can't change contents of colors.xml at runtime, instead you can keep all possible colors in it and use getIdentifier to get the color returned by API. You can keep the chosen color in SharedPref and retrieve it later in next session till API call finishes – Sourabh Nov 29 '15 at 20:33
  • Ya, but how to set the textColor of a textView or button? if I have 10 Textviews and I want to put them on green text I have to put the setText(java) for each and asigning the value from SharedPreferences?... – Skizo-ozᴉʞS ツ Nov 29 '15 at 20:49
  • Yep, that's the only way. And besides, changing Resources can later lead to all sorts of confusion in your app – Sourabh Nov 29 '15 at 20:56
3

store hex color codes into sharedpreferences and then use parsecolor function store your all hexcodes of colors into sessions as a string and whenever you want to change color of perticular button ,textview..just retrive that color code from session and use it as
for ex.
session.setString("white","#FFFFFF"); String colorname=session.getString("white");yourtextview.setBackgroundColor(Color.parseColor(colorname);