14

I want to create a static Hash table to convert strings to integers. The caveat here is that I want to use strings I defined as resources in several lists in XML files.

I am able to write this, using only resources IDs:

public class MyActivity extends Activity {

private static Map<Integer, Integer> views = new HashMap<Integer, Integer>();
static {
    views.put(R.string.full_text, MessageTable.VIEW_FULL);
    views.put(R.string.only_text, MessageTable.VIEW_MSG);
    views.put(R.string.tag_text, MessageTable.VIEW_TAGMSG);
}

I get no errors, but what I would really need to do is something like this:

public class MyActivity extends Activity {

private static Map<String, Integer> views = new HashMap<String, Integer>();
static {
    views.put(getResources().getString(R.string.full_text), MessageTable.VIEW_FULL);
    views.put(getResources().getString(R.string.only_text), MessageTable.VIEW_MSG);
    views.put(getResources().getString(R.string.tag_text), MessageTable.VIEW_TAGMSG);
}

which gives me the following error in Eclipse:

Cannot make a static reference to the non-static method getResources() from the type ContextWrapper

The message makes sense, but what does not makes sense is that resources are unreachable from the static block, one would think static variables were custom created to make use of resources.
I guess I can populate the Hash table during the object constructor, but this would mean to do it in the wrong place.

ilomambo
  • 8,290
  • 12
  • 57
  • 106
  • Why do you want to load all that up in a hash map? Wouldn't it be fine just to reference the resource and load it into a string when you need it non-statically? I guess I don't understand the application completely. – shibbybird Apr 17 '12 at 16:57

5 Answers5

12

getResources() (short for ~ MyActivity.this.getResources()) requires a context object which is not initialized at that time. Since context is only available after you hit onCreate you can't even do it at construction time of MyActivity.

The reason is that the activity manager which instantiates your MyActivity class has to determine the configuration (orientation, screen size, language, ...) before he knows which resources have to be extracted from the xml. -> Resources are not static and can't be accessed from static context.

So I guess there is no way around doing those operations in onCreate or later.

Edit: While you certainly can update the static HashMap (or a static Context) from onCreate I wouldn't recommend that since you can have several instances of the same Activity with possibly different / changing configurations. Also storing a static Context is a great way to create a Memory Leak

Rafa Viotti
  • 9,998
  • 4
  • 42
  • 62
zapl
  • 63,179
  • 10
  • 123
  • 154
  • I think resources are the most static elements in an application. Otherwise how do you explain that accessing R.string.stringname is fine? I get your explanation on why there is an error, I hope someone might know a bypass to get to the strings. – ilomambo Apr 17 '12 at 17:09
  • 1
    those `R.whatever` things are created by android's build tools at compilation time. They are just `int` ids that refer to your resources. The actual content that is retrieved for that id is determined based on configuration. That's required since you can / should have multiple definitions for the same id, e.g. Strings in `res/values/strings.xml`, `res/values-es/strings.xml`, `res/values-land-mdpi/strings.xml` – zapl Apr 17 '12 at 17:16
  • Keeping the applicaton context can't lead to memory leak. However, keeping a specific context can. – asenovm Apr 17 '12 at 17:22
  • @MartinAsenov ok, keeping application context around is pretty safe but might lead to leaks if use it wrong. From the docs: "However using the ApplicationContext elsewhere can easily lead to serious leaks if you forget to unregister, unbind, etc." – zapl Apr 17 '12 at 17:50
  • If strings are not translatable they can be accessed in a static way thanks to system resources, see [this SO response](http://stackoverflow.com/a/8765789/831591) for details. This is used by AOSP to overwrite default configuration by overlays, search for [config_wifi_tether_enable](http://androidxref.com/6.0.0_r1/search?q=config_wifi_tether_enable&defs=&refs=&path=&hist=&project=frameworks) as example. – Jeremy Rocher Dec 02 '15 at 12:45
  • @JeremyRocher the important bit is "for system resources only", that is `android.R.*`. In other words nothing from your own `res` directory (even if you don't provide translations) which is what people usually want. – zapl Dec 02 '15 at 13:04
8
public Static Resources mResources;
public MyApplication extends Application
{
     @Override
     public void onCreate()
     {
           mResources = getResources();
     }

}

Once you have the static reference to the Resources, you can refer to it anywhere from the entire application. However i am not sure about whether this is gonna introduce a momory leak or not;

Khurram Shehzad
  • 1,010
  • 12
  • 16
1

One thing you can do is create an Application class and register it in your AndroidManifest.xml. Then override the onCreate method and set the Application class as a static reference and then touch the Activity class to run the static initializer. The Application class will be run as the apk is loaded into memory so it will always run before any Activity.

There are some significant drawbacks to this. The most obvious one I think of is if the system language changes and you have added translations to these resources; then you will have inconsistent strings because the application will use the default/whatever the language was when the application was launched. String resources are influenced by Android's resource management system so changes to things like orientation, system language, and screen dimensions can influence what these values are. That's the reason why Activities get reset when

In short, you're better off using constant Strings key-value pairs for this job. If you need to use string resources so you can handle translations better. I'd load them each time the Activity starts. Otherwise, you risk memory leaks and inconsistent string translations.

public MyApplication extends Application {

  public static Context GlobalContext = null;
  
  @Override
  protected void onCreate() {
    MyApplication.GlobalContext = this;
    
    // Touch the activity class.
    MyActivity activity = new MyActivity();
    // Throw it away by not using it.
    
    // invalidate handle to prevent GC leaks.
    GlobalContext = null;
  }
}

public class MyActivity extends Activity {
  private static Map<String, Integer> views = new HashMap<String, Integer>();

  static {
    Context context = MyApplication.GlobalContext;
    Resources res = context.getResources();
    views.put(res.getString(R.string.full_text), MessageTable.VIEW_FULL);
    views.put(res.getString(R.string.only_text), MessageTable.VIEW_MSG);
    views.put(res.getString(R.string.tag_text), MessageTable.VIEW_TAGMSG);
  }
}
Fakhar
  • 3,946
  • 39
  • 35
Jeremy Edwards
  • 14,620
  • 17
  • 74
  • 99
  • I get you. The situation is not what I hoped for, but I'll have to work with what's at hand. Thanks – ilomambo Apr 18 '12 at 13:12
0

You can always keep a static reference to the ApplicationContext. A possible way is described here: Static way to get 'Context' on Android?

Community
  • 1
  • 1
asenovm
  • 6,397
  • 2
  • 41
  • 52
  • Same as above, in your link the context is set only during onCreate(). I need the resources immediately to construct the map, maybe before onCreate(). – ilomambo Apr 17 '12 at 17:16
  • But you made me think and I actually have a related similar question, Can you define a map in XML? If this is possible then I surely have my resources available in the XML file. I think I will post it as a new question, since it deserves its own post. – ilomambo Apr 17 '12 at 17:19
0

I don't know what's the BEST way, But, for example, in my app, I have a Singleton (called GuiaUtil) that aways keeps my current Activity and Context.

private static Map<String, Integer> views = new HashMap<String, Integer>();
static {
views.put(GuiaUtil.instance().getAppContext().getResources().getString(R.string.full_text), MessageTable.VIEW_FULL);
views.put(GuiaUtil.instance().getAppContext().getResources().getString(R.string.only_text), MessageTable.VIEW_MSG);
views.put(GuiaUtil.instance().getAppContext().getResources().getString(R.string.tag_text), MessageTable.VIEW_TAGMSG);
}
Andre Mariano
  • 308
  • 1
  • 3
  • 14
  • Is this an code lookalike of yours that actually works? Because it seems to me that you should get an error for getAppContext(), since the context might not yet be set when this static block is executed. – ilomambo Apr 17 '12 at 17:15
  • If you looking to put it in your first Activity, then It probably won't work because You didn't set the Singleton yet... as I said, it's obviously not the best way to do this, but It's a start. Anyway, You can away create your static HashMap on the "onCreate" checking if it's not null before you instanciate it. – Andre Mariano Apr 17 '12 at 17:45