2

I'm finding the accepted answer and discussion for this SO question getString Outside of a Context or Activity unclear.

I'm new to Android and I'm trying to understand how I can reference my resource strings in a model class so that I can properly support localization.

Specifically, my model has a Location property and I would like to be able to return a string for the compass ordinal for the bearing. Because compass ordinals like "North" need to be localized I'm trying to store them in my strings.xml.

I think I understand that I need the application context to get to the resources object but I'm wondering if this is possible without having to pass the context in. Storing a UI context in a model seems like a violation of MVC.

To accomplish this I wanted to include a method in my model like this. The first if shows how I'm trying to use the strings.xml entry.

public String compassOrdinalForBearing(float bearing) {

assert bearing >= 0.0 && bearing <= 360.0;

if ((bearing > 336.5) && (bearing <= 360.0))
    //Problem here
    return Context.getResources().getString(R.string.compass_ordinal_north);
else if ((bearing >= 0) && (bearing <= 22.5))
    return "North";
else if ((bearing > 22.5)  && (bearing <= 67.5))
    return "Northeast";
else if ((bearing > 67.5)  && (bearing <= 112.5))
    return "East";
else if ((bearing > 112.5) && (bearing <= 157.5))
    return "Southeast"; 
else if ((bearing > 157.5) && (bearing <= 202.5))
    return "South";
else if ((bearing > 202.5) && (bearing <= 247.5))
    return "Southwest";
else if ((bearing > 247.5) && (bearing <= 292.5))
    return "West";
else if ((bearing > 292.5) && (bearing <= 337.5))
    return "Northwest";
else
    assert false;
    return null;
}
Community
  • 1
  • 1
Nick
  • 8,483
  • 10
  • 46
  • 65

2 Answers2

3

Often what is done is subclassing the Application class, which is guaranteed to have only one instance.

Application subclass:

public class MyApplication extends Application {
   private static Context mContext;

   @Override
   public void onCreate(){
      super.onCreate();
      mContext = this;
   }

   public static Context getContext(){
        return mContext;
   }
}

Your class:

public String compassOrdinalForBearing(float bearing) {
    Context context = MyApplication.getContext();

    String north = context.getResources().getString(R.string.compass_ordinal_north);)
}

But don't forget to change the Manifest:

<application android:name="com.example.myapp.MyApplication">

Alternatively you can just pass in a context during instantiation, without keeping a pointer to it, as almost surely these objects will be instantiated from a context.

private Static string NORTH = null;

public MyClass(Context context){
    initializeDirections(context);
}

private static void initializeDirections(Context context){
    if(NORTH == null){
        NORTH = context.getResources().getString(R.string.compass_ordinal_north);
    }
}

Finally a kind of messy combination of the two just in case you genuinely cannot pass in a context upon instantiation and you do not want to keep the application context in the Application subclass:

public class MyApplication extends Application {
    @Override
    public void onCreate(){
        super.onCreate();
        MyClass.initializeDirections(this);
    }
}

public class MyClass{
    private static String NORTH = null;

    public static final void initializeDirections(Context context){
        NORTH = context.getResources().getString(R.string.compass_ordinal_north);
    }
}

Edit: On an unrelated note, at least from only this only snippet, you can kill all of the first conditionals. If it has reached that particular 'else', the first conditional is necessarily true.

For example:

else if ((bearing >= 0) && (bearing <= 22.5))
    return "North";
else if ((bearing > 22.5)  && (bearing <= 67.5))
    return "Northeast";

Could be:

else if (bearing <= 22.5)
    return "North";
else if (bearing <= 67.5)
    return "Northeast";

As if bearing is !<= 22.5, it is necessarily > 22.5.

This may or may not improve readability to you and may or may not be desirable to you. Just some probably unwanted two cents :)

panda
  • 210
  • 3
  • 8
  • This might be the most helpful answer I've ever recieved on SO. Thank you for detailing the different approaches. The application subclassing suits my needs perfectly for now. On the If block I just spell the ranges out explictly but yes it's not the most efficent way to evaluate things- balancing between readability and efficency is always tricky it's a good point about minimizing this. Thanks again for the excellent write up. – Nick Oct 05 '11 at 22:26
0

but I'm wondering if this is possible without having to pass the context in

No, you do need to pass the context in.

I suppose the only place where you need localized string resources is some UI to be presented to users. UI is always a part of some Activity (which is also Context), so it should not be a problem to have a Context instance when you need to get localized strings for your model.

Vit Khudenko
  • 28,288
  • 10
  • 63
  • 91
  • So if I create say 500 model instances. Storing a context instance in each is normal? – Nick Oct 05 '11 at 19:21
  • @Nick: Of course, you should not keep the reference to Context instance in the model. You need it only to generate UI representation for the model. So some code in Activity should be able to get the localized UI representation from the model. To get `Resources` instance you need `Context` instance. To optimize the code a bit you may pass `Resources` instance to model instead of `Context` instance. – Vit Khudenko Oct 05 '11 at 20:10